2009-07-07 18 views
19

Ho una classe A e una classe B che eredita la classe A e la estende con altri campi.Come "clonare" un oggetto in un oggetto sottoclasse?

Avere un oggetto di tipo aA, come posso creare un oggetto di tipo bB che contiene tutti i dati che si oppongono a contenute?

Ho provato a.MemberwiseClone() ma mi dà solo un altro tipo di oggetto A. E non posso trasmettere A in B poiché la relazione di ereditarietà consente solo il cast opposto.

Qual è il modo giusto per farlo?

+0

Grazie per le risposte. Stavo cercando un modo automatico per questo, ma stai suggerendo che non esiste un tale. :( – Vizu

+1

Purtroppo no, dovrai aggiungere un costruttore o un metodo factory di qualche forma: –

+0

@Vizu quale metodo hai adottato, voglio anche qualcosa di simile, per favore mettilo qui se lo hai –

risposta

8

non ci sono mezzi per farlo costruita automaticamente nella lingua ...

Una possibilità è quella di aggiungere un costruttore per la classe B che prende una classe A come argomento.

allora si potrebbe fare:

B newB = new B(myA); 

Il costruttore può semplicemente copiare i dati rilevanti attraverso, se necessario, in questo caso.

+0

è possibile, copiare una sottoclasse in super-classe che ha alcune delle similitudini simili –

1

Creare un agente in B che consenta di passare un oggetto di tipo A, quindi copiare i campi A e impostare i campi B in base alle esigenze.

0

È possibile eseguire un metodo di conversione sulla classe B che include la classe base.

public ClassB Convert(ClassA a) 
{ 
    ClassB b = new ClassB(); 
    // Set the properties 
    return b; 
} 

Si potrebbe anche avere un costruttore per ClassB in un oggetto di ClasseA.

11

Vorrei aggiungere un costruttore di copia ad A, quindi aggiungere un nuovo costruttore a B che prende un'istanza di A e lo passa al costruttore di copie della base.

+2

La risposta accettata è ciò che ho sempre fatto, ma questo twist è semplice ed elegante – JMD

0

No, non puoi farlo. Un modo per ottenere ciò è aggiungere un costruttore sulla classe B che accetta un parametro di tipo B e aggiungere i dati manualmente.

Così si potrebbe avere qualcosa di simile:

public class B 
{ 
    public B(A a) 
    { 
    this.Foo = a.foo; 
    this.Bar = a.bar; 
    // add some B-specific data here 
    } 
} 
+3

Non sono d'accordo sul fatto che dovresti avere un metodo Clone() su A che restituisce un B, poiché introduce una dipendenza circolare –

+0

Sono d'accordo con il costruttore per la classe B, ma perché hai bisogno di CloneToB() metodo? –

+0

ehm sì, hai ragione, non avrei dovuto includere quel metodo. L'ho incluso perché il poster menzionava MemberWiseCl uno(). Ad ogni modo, questo codice non è buono e lo eliminerò. Grazie. – Razzie

2

Utilizzando Factory Method Pattern:

private abstract class A 
    { 
     public int A1 { get; set; } 

     public abstract A CreateInstance(); 

     public virtual A Clone() 
     { 
      var instance = CreateInstance(); 
      instance.A1 = A1; 
      return instance; 
     } 
    } 

    private class B : A 
    { 
     public int A3 { get; set; } 

     public override A CreateInstance() 
     { 
      return new B(); 
     } 

     public override A Clone() 
     { 
      var result = (B) base.Clone(); 
      result.A3 = A3; 
      return result; 
     } 
    } 

    private static void Main(string[] args) 
    { 
     var b = new B() { A1 = 1, A3 = 2 }; 

     var c = b.Clone(); 
    } 
3

È possibile raggiungere questo obiettivo utilizzando riflessione.

Vantaggio: Manutenibilità. Non c'è bisogno di cambiare copy-constructor o simili, aggiungere o rimuovere proprietà.

Svantaggio: prestazioni. La riflessione è lenta. Stiamo ancora parlando di millisecondi su classi di medie dimensioni.

Ecco un'implementazione copia superficiale riflessione a base di supporto copy-to-sottoclasse, utilizzando metodi di estensione:

public static TOut GetShallowCopyByReflection<TOut>(this Object objIn) 
{ 
    Type inputType = objIn.GetType(); 
    Type outputType = typeof(TOut); 
    if (!outputType.Equals(inputType) && !outputType.IsSubclassOf(inputType)) throw new ArgumentException(String.Format("{0} is not a sublcass of {1}", outputType, inputType)); 
    PropertyInfo[] properties = inputType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); 
    FieldInfo[] fields = inputType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy); 
    TOut objOut = (TOut)Activator.CreateInstance(typeof(TOut)); 
    foreach (PropertyInfo property in properties) 
    { 
     try 
     { 
      property.SetValue(objIn, property.GetValue(objIn, null), null); 
     } 
     catch (ArgumentException) { } // For Get-only-properties 
    } 
    foreach (FieldInfo field in fields) 
    { 
     field.SetValue(objOut, field.GetValue(objIn)); 
    } 
    return objOut; 
} 

Questo metodo copia tutte le proprietà - privata e pubblica, così come tutti i campi. Le proprietà vengono copiate per riferimento, rendendola una copia superficiale.

Prove di unità:

[TestClass] 
public class ExtensionTests { 
    [TestMethod] 
    public void GetShallowCloneByReflection_PropsAndFields() 
    { 
     var uri = new Uri("http://www.stackoverflow.com"); 
     var source = new TestClassParent(); 
     source.SomePublicString = "Pu"; 
     source.SomePrivateString = "Pr"; 
     source.SomeInternalString = "I"; 
     source.SomeIntField = 6; 
     source.SomeList = new List<Uri>() { uri }; 

     var dest = source.GetShallowCopyByReflection<TestClassChild>(); 
     Assert.AreEqual("Pu", dest.SomePublicString); 
     Assert.AreEqual("Pr", dest.SomePrivateString); 
     Assert.AreEqual("I", dest.SomeInternalString); 
     Assert.AreEqual(6, dest.SomeIntField); 
     Assert.AreSame(source.SomeList, dest.SomeList); 
     Assert.AreSame(uri, dest.SomeList[0]);    
    } 
} 

internal class TestClassParent 
{ 
    public String SomePublicString { get; set; } 
    internal String SomeInternalString { get; set; } 
    internal String SomePrivateString { get; set; } 
    public String SomeGetOnlyString { get { return "Get"; } } 
    internal List<Uri> SomeList { get; set; } 
    internal int SomeIntField; 
} 

internal class TestClassChild : TestClassParent {} 
+0

Che cosa conta come dimensione media della classe? –

+0

@ adam-l-s Hehe buona domanda. La mia risposta come sempre quando si tratta di prestazioni: Misura. Se è abbastanza veloce per te, usalo. Si dice che la riflessione sia 1000 volte più lenta dell'accesso alle proprietà nel modo normale: http://stackoverflow.com/questions/25458/how-costly-is-net-reflection – Nilzor

0

Nella tua classe di base aggiungere il metodo virtuale CreateObject sotto ...

public virtual T CreateObject<T>() 
    { 
     if (typeof(T).IsSubclassOf(this.GetType())) 
     { 
      throw new InvalidCastException(this.GetType().ToString() + " does not inherit from " + typeof(T).ToString()); 
     } 

     T ret = System.Activator.CreateInstance<T>(); 

     PropertyInfo[] propTo = ret.GetType().GetProperties(); 
     PropertyInfo[] propFrom = this.GetType().GetProperties(); 

     // for each property check whether this data item has an equivalent property 
     // and copy over the property values as neccesary. 
     foreach (PropertyInfo propT in propTo) 
     { 
      foreach (PropertyInfo propF in propFrom) 
      { 
       if (propT.Name == propF.Name) 
       { 
        propF.SetValue(ret,propF.GetValue(this)); 
        break; 
       } 
      } 
     } 

     return ret; 
    } 

poi dire che si desidera creare un oggetto sottoclasse di vita reale dalla classe super-solo chiama

this.CreateObject<subclass>(); 

Questo dovrebbe farlo!

0

Mentre nessuno ha suggerito questo (e questo non funzionerà per tutti, ammettiamolo), si dovrebbe dire che se si ha la possibilità di creare l'oggetto b dal get-go, farlo invece di creare l'oggetto a allora copiare sull'oggetto b. Per esempio, immaginate di essere nella stessa funzione e hanno questo codice:

var a = new A(); 
a.prop1 = "value"; 
a.prop2 = "value"; 
... 
// now you need a B object instance... 
var b = new B(); 
// now you need to copy a into b... 

Invece di preoccuparsi di quell'ultimo passo commentato, basta iniziare con B e impostare i valori:

var b = new B(); 
b.prop1 = "value"; 
b.prop2 = "value"; 

Si prega di Don mi manda in su perché pensi che quanto sopra sia stupido! Ho incontrato molti programmatori che sono così concentrati sul loro codice da non rendersi conto che una soluzione più semplice li sta guardando in faccia. :)

Problemi correlati