2010-04-14 3 views
1

Dire que j'ai cette classe MyClass qui contient cette méthode:C# réflexion, le clonage

public class MyClass 
    { 
     public int MyProperty { get; set; } 

     public int MySecondProperty { get; set; } 

     public MyOtherClass subClass { get; set; } 

     public object clone<T>(object original, T emptyObj) 
     { 

      FieldInfo[] fis = this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 


      object tempMyClass = Activator.CreateInstance(typeof(T)); 


      foreach (FieldInfo fi in fis) 
      { 
       if (fi.FieldType.Namespace != original.GetType().Namespace) 
        fi.SetValue(tempMyClass, fi.GetValue(original)); 
       else 
        fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original))); 
      } 

      return tempMyClass; 
     } 
} 

Puis cette classe:

public class MyOtherClass 
{ 
    public int MyProperty777 { get; set; } 
} 

quand je fais ceci:

MyClass a = new MyClass { 
         MyProperty = 1, 
         MySecondProperty = 2, 
         subClass = new MyOtherClass() { MyProperty777 = -1 } 
         }; 
      MyClass b = a.clone(a, a) as MyClass; 

comment se sur le deuxième appel à cloner, T est de type objet et non de type MyOtherClass

+0

Quel appel à 'clone' faites-vous référence? –

Répondre

3

Votre deuxième appel (récursif) à clone passe le résultat de GetValue comme deuxième argument, qui est de type object, et par conséquent T est object.

à-dire

fi.SetValue(tempMyClass, this.clone(fi.GetValue(original), fi.GetValue(original))); 

Le résultat de GetValue sur un FieldInfo est un object. Étant donné que vous passez deux fois la même chose dans tous les cas, la conception de la méthode clone est peut-être erronée. Vous n'avez probablement pas besoin de génériques ici. Utilisez simplement obj.GetType() pour obtenir les informations de type du deuxième argument (si vous avez vraiment besoin d'un second argument).

Il serait plus sensé de contraindre le type de retour en utilisant des génériques, de sorte que la distribution n'est pas nécessaire du côté appelant. En outre, vous pourriez faire de Clone une méthode d'extension afin qu'elle puisse s'appliquer à n'importe quoi. D'autre part, la chose que vous essayez de faire (un clone profond automatique) est peu susceptible d'être généralement utile. La plupart des classes finissent par contenir des références à des choses qui ne leur appartiennent pas, donc si vous clonez un tel objet, vous finirez par cloner accidentellement la moitié de votre framework d'application.

1

Essayez ceci:


    public static class Cloner 
    { 
     public static T clone(this T item) 
     { 
      FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
      object tempMyClass = Activator.CreateInstance(item.GetType()); 
      foreach (FieldInfo fi in fis) 
      { 
       if (fi.FieldType.Namespace != item.GetType().Namespace) 
        fi.SetValue(tempMyClass, fi.GetValue(item)); 
       else 
       { 
        object obj = fi.GetValue(item); 
        fi.SetValue(tempMyClass, obj.clone()); 
       } 
      }  
      return (T)tempMyClass; 
     } 
    } 


MyClass b = a.clone() as MyClass; 
0

D'abord, je suis d'accord que la méthode clone doit être statique, mais je ne pense pas que

object tempMyClass = Activator.CreateInstance(typeof(T)); 

est une bonne idée. Je pense que le meilleur moyen est d'utiliser le type d'original et de se débarrasser du paramètre emptyObject.

object tempMyClass = Activator.CreateInstance(original.GetType()); 

Aussi, vous devez GetFields sur original pas this.

donc ma méthode serait

public static T clone<T>(T original) 
{ 
    T tempMyClass = (T)Activator.CreateInstance(original.GetType()); 

    FieldInfo[] fis = original.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 
    foreach (FieldInfo fi in fis) 
    { 
     object fieldValue = fi.GetValue(original); 
     if (fi.FieldType.Namespace != original.GetType().Namespace) 
      fi.SetValue(tempMyClass, fieldValue); 
     else 
      fi.SetValue(tempMyClass, clone(fieldValue)); 
    } 

    return tempMyClass; 
} 

Notez que j'utiliser original.GetType() de toute façon que l'appel intérieur aurait type T = Object de toute façon. Type générique utilisé est déterminé au moment de la compilation et il serait Object comme type de retour de fi.GetValue.

Vous pouvez déplacer cette méthode statique vers une classe d'assistance statique.En guise de note, je voudrais dire que cette implémentation de clone "deep" ne fonctionnera pas correctement s'il y a un champ de type collection (ou un champ composite mutable standard) dans l'une des classes de votre espace de noms.

0

La meilleure façon de cloner une instance d'une classe est de créer un délégué pour le faire. En effet, le délégué produit par l'expression linq peut accéder aux champs private/internal/protected et public. Le délégué ne peut être créé qu'une seule fois. Gardez-le dans un champ statique en classe générique pour profiter de la résolution de recherche générique au lieu du dictionnaire

/// <summary> 
/// Help to find metadata from expression instead of string declaration to improve reflection reliability. 
/// </summary> 
static public class Metadata 
{ 
    /// <summary> 
    /// Identify method from method call expression. 
    /// </summary> 
    /// <typeparam name="T">Type of return.</typeparam> 
    /// <param name="expression">Method call expression.</param> 
    /// <returns>Method.</returns> 
    static public MethodInfo Method<T>(Expression<Func<T>> expression) 
    { 
     return (expression.Body as MethodCallExpression).Method; 
    } 
} 

/// <summary> 
/// Help to find metadata from expression instead of string declaration to improve reflection reliability. 
/// </summary> 
/// <typeparam name="T">Type to reflect.</typeparam> 
static public class Metadata<T> 
{ 
    /// <summary> 
    /// Cache typeof(T) to avoid lock. 
    /// </summary> 
    static public readonly Type Type = typeof(T); 

    /// <summary> 
    /// Only used as token in metadata expression. 
    /// </summary> 
    static public T Value { get { throw new InvalidOperationException(); } } 
} 



/// <summary> 
/// Used to clone instance of any class. 
/// </summary> 
static public class Cloner 
{ 
    /// <summary> 
    /// Define cloner implementation of a specific type. 
    /// </summary> 
    /// <typeparam name="T">Type to clone.</typeparam> 
    static private class Implementation<T> 
     where T : class 
    { 
     /// <summary> 
     /// Delegate create at runtime to clone. 
     /// </summary> 
     static public readonly Action<T, T> Clone = Cloner.Implementation<T>.Compile(); 

     /// <summary> 
     /// Way to emit delegate without static constructor to avoid performance issue. 
     /// </summary> 
     /// <returns>Delegate used to clone.</returns> 
     static public Action<T, T> Compile() 
     { 
      //Define source and destination parameter used in expression. 
      var _source = Expression.Parameter(Metadata<T>.Type); 
      var _destination = Expression.Parameter(Metadata<T>.Type); 

      //Clone method maybe need more than one statement. 
      var _body = new List<Expression>(); 

      //Clone all fields of entire hierarchy. 
      for (var _type = Metadata<T>.Type; _type != null; _type = _type.BaseType) 
      { 
       //Foreach declared fields in current type. 
       foreach (var _field in _type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)) 
       { 
        //Assign destination field using source field. 
        _body.Add(Expression.Assign(Expression.Field(_destination, _field), Expression.Field(_source, _field))); 
       } 
      } 

      //Compile expression to provide clone method. 
      return Expression.Lambda<Action<T, T>>(Expression.Block(_body), _source, _destination).Compile(); 
     } 
    } 

    /// <summary> 
    /// Keep instance of generic definition of clone method to improve performance in reflection call case. 
    /// </summary> 
    static private readonly MethodInfo Method = Metadata.Method(() => Cloner.Clone(Metadata<object>.Value)).GetGenericMethodDefinition(); 

    static public T Clone<T>(T instance) 
     where T : class 
    { 
     //Nothing to clone. 
     if (instance == null) { return null; } 

     //Identify instace type. 
     var _type = instance.GetType(); 

     //if T is an interface, instance type might be a value type and it is not needed to clone value type. 
     if (_type.IsValueType) { return instance; } 

     //Instance type match with generic argument. 
     if (_type == Metadata<T>.Type) 
     { 
      //Instaitate clone without executing a constructor. 
      var _clone = FormatterServices.GetUninitializedObject(_type) as T; 

      //Call delegate emitted once by linq expreesion to clone fields. 
      Cloner.Implementation<T>.Clone(instance, _clone); 

      //Return clone. 
      return _clone; 
     } 

     //Reflection call case when T is not target Type (performance overhead). 
     return Cloner.Method.MakeGenericMethod(_type).Invoke(null, new object[] { instance }) as T; 
    } 
} 
+0

une brève explication de ce que vous faites aidera le questionneur à mieux comprendre –

+0

La meilleure façon de cloner une instance d'une classe est de créer un délégué pour le faire. En effet, le délégué produit par l'expression linq peut accéder aux champs private/internal/protected et public. Le délégué ne peut être créé qu'une seule fois. Gardez-le dans un champ statique dans une classe générique pour bénéficier de la résolution de recherche générique au lieu du dictionnaire. –

+0

S'il vous plaît modifier votre réponse et ajouter votre explication à elle. – Gosu