【问题标题】:Can one copy the contents of one object to another dynamically if they have the same interface?如果它们具有相同的接口,可以将一个对象的内容动态复制到另一个对象吗?
【发布时间】:2010-07-23 14:44:14
【问题描述】:

例如,如果我有两个对象,一个是 Monkey 类型,另一个是 Dog 类型,它们都实现了 IAnimal,如下所示:

interface IAnimal
{
  int numberOfEyes {get; set;}
  string name {get; set;}
}

我想做这样的事情:

Monkey monkey = new Monkey() { numberOfEyes = 7, name = "Henry" };
Dog dog = new Dog();
MyFancyClass.DynamicCopy(monkey, dog, typeof(IAnimal));
Debug.Assert(dog.numberOfEyes == monkey.numberOfEyes);

我想可以使用反射创建一个像 MyFancyClass 这样的类...任何聪明的人都有想法?

谢谢, 斯蒂芬

【问题讨论】:

  • 你完全可以使用反射来做到这一点。这也是一个非常糟糕的主意。我认为在这种情况下,解释你为什么要这样做可以让人们帮助你找到更好的解决方案。
  • 顺便说一句:您可以在接口中定义属性,但不能在字段中定义(如您的代码示例中所示)。在您的情况下,所有属性都应同时具有 setget 访问器。
  • 您使用的是 .NET 4 吗?最新框架中有更多强大的 ExpressionTree 选项,它们可以提供性能良好的基于​​反射的解决方案。
  • jdmichal ...出于懒惰...与其拥有一堆interface1.value = interface2.value,我更喜欢动态的方式来做这件事。 stakx ... yip .. 我忘记了 { set;得到; } ...哎呀 :) Greg - .Net 3.5

标签: c# .net


【解决方案1】:

只是把它混在一起......你也可以使用AutoMapper将一个对象映射/复制到另一个......他们甚至不必实现相同的接口。要使其自动工作,只需属性的名称必须匹配,然后您只需执行以下操作:

Mapper.Map<IAnimal, MyClass>(myInstance);

【讨论】:

    【解决方案2】:

    以下是基于反射的解决方案。请注意,每个 Type 只进行一次反射工作,然后进行缓存,因此开销应该是最小的。将与 .NET 3.5 一起使用,并且不限于接口。

    请注意,我使用反射来获取类型 T 的所有属性,并过滤到同时具有 getter 和 setter 的属性。然后,我为从源中检索值并将该值分配给目标的每个属性构建一个表达式树。表达式树被编译并缓存在一个静态字段中。当调用 CopyProperties 方法时,它会为每个属性调用复制器,复制类型 T 中定义的所有属性。

    // Usage
    Monkey monkey = new Monkey() { numberOfEyes = 7, name = "Henry" };
    Dog dog = new Dog();
    DynamicCopy.CopyProperties<IAnimal>(monkey, dog);
    Debug.Assert(dog.numberOfEyes == monkey.numberOfEyes);
    
    ...    
    
    // The copier
    public static class DynamicCopy
    {
        public static void CopyProperties<T>(T source, T target)
        {
            Helper<T>.CopyProperties(source, target);
        }
    
        private static class Helper<T>
        {
            private static readonly Action<T, T>[] _copyProps = Prepare();
    
            private static Action<T, T>[] Prepare()
            {
                Type type = typeof(T);
                ParameterExpression source = Expression.Parameter(type, "source");
                ParameterExpression target = Expression.Parameter(type, "target");
    
                var copyProps = from prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                where prop.CanRead && prop.CanWrite
                                let getExpr = Expression.Property(source, prop)
                                let setExpr = Expression.Call(target, prop.GetSetMethod(true), getExpr)
                                select Expression.Lambda<Action<T, T>>(setExpr, source, target).Compile();
    
                return copyProps.ToArray();
            }
    
            public static void CopyProperties(T source, T target)
            {
                foreach (Action<T, T> copyProp in _copyProps)
                    copyProp(source, target);
            }
        }
    }
    

    【讨论】:

    • +1 - 不错的答案。明智地使用反射可以很好地发挥作用。
    【解决方案3】:

    复制构造函数是我通常做的:

    class Monkey : IAnimal
    {
      public Monkey(IAnimal other)
      {
        //Copy properties here...
      }
    }
    

    【讨论】:

    • 不应该复制构造函数在IAnimal 级别,并且Monkey 复制构造函数委托给那个吗?否则,你会破坏封装。
    • 除了接口没有构造函数。如果它是一个抽象类,那么我会说是的,基类应该有它自己的复制构造函数,派生类调用它。
    • 我认为这样的事情保证IAnimal 成为一个抽象基类而不是一个接口。虽然我真的很喜欢你在我的评论中发布的扩展方法想法。
    【解决方案4】:

    你有几个选择:

    您可以选择使用反射,但这会比其他选项慢得多,而且您必须编写自己的反射代码。使用反射制作漂亮的通用“克隆”代码非常重要,尤其是当您必须开始处理包含其他对象实例的列表/数组/字典的对象时。

    正如 Herbie 博士所说,复制构造函数是一种选择。

    另一种方法是在所有类型上实现ICloneable(您可以让接口实现ICloneable 以强制所有IAnimals 实现它)。这可能不像反射那样是动态的(你必须为每个类手工制作它),但假设你只是复制属性值,它会比反射快得多。

    同样值得考虑的是不变性。如果您可以使您的具体类型不可变(在所有字段上使用readonly 以便它们无法更改),那么您可能根本不需要担心克隆。一切都可以安全地共享同一个实例,因为其他共享者无法以任何方式对其进行修改。这种不变性可能非常强大,但如果您的界面包含可以修改的集合/数组,您需要小心。

    最后,如果您有很多类,您可以查看代码生成以生成 C#“克隆”类(其工作是生成给定类型的克隆)并将它们编译成程序集。您可以在此处使用反射来创建“克隆类模板”,但由于它会生成代码(与项目的其余部分一起编译),因此您不会受到慢反射的运行时影响。

    因此,克隆有很多选择 - 但使用反射,即使它可以是天真的和动态的,通常也不是最好的方法。

    【讨论】:

    • 反射不必很慢。我经常使用反射来构建表达式树,将其编译为 lambda 函数,并缓存委托。有一次性构建成本,但所有后续调用都应与普通代码一样快。此处示例:stackoverflow.com/questions/2685046/…
    • @Greg - 你当然是对的,这是一个很好的技巧(尽管委托调用仍然可能比本地调用慢一个数量级,但它比单独的反射要快得多) .不过,我想表达的是,编写一个像样的通用克隆器是“不平凡的”,在深入思考之前还有其他方法值得研究。
    • 同意。如果项目的规模使其他方法劳动密集或容易出错,我只会建议使用花哨的反射技术。
    【解决方案5】:

    您可以使 IAnimal 实现 ICloneable。然后对猴子或其他实现 ICloneable 的类进行 memeberwise 克隆。顺便说一下,这是一个浅拷贝。

    public interface IAnmial : ICloneable
    {
        string Name{get; set;}
        object Clone();
    }
    
    public class Monkey : IAnmial
    {
        public string Name{get; set;}
    
        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }
    
    public class Dog : IAnmial
    {
        public string Name{get; set;}
    
        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }
    
    public class Test()
    {
        public void CloneAnimal()
        {
            Dog dog = new Dog()
            {
                Name = "MyAnimal",
            };
            IAnimal monkey = dog.Clone() as IAnimal;
        }
    }
    

    【讨论】:

    • dog.Clone() 返回一个 Dog 类型的对象,它不能被类型转换为 Monkey。此解决方案行不通。
    • 你说得对,我的意思是:IAnimal monkey = dog.Clone() as IAnimal;
    • 这仍然是错误的。代码IAnimal monkey = dog.Clone() as IAnimal; 创建了Dog 的新实例,而不是Monkey
    【解决方案6】:

    只要DynamicCopy 接受IAnimal 作为方法参数,您就可以做到这一点。 但这确实有助于了解您要做什么。

    【讨论】:

      【解决方案7】:

      为什么不在IAnimal 中实现一个方法?

      (编辑:正如评论者有帮助指出的那样,IAnimal 应该被转换为这个解决方案的抽象基类Animal。无论如何这都是有道理的,因为这个问题背后的整个推论是子类包含父类中定义的属性。)

      // In Aminal class.
      public void CopyAttributes(Animal source)
      {
          this.numberOfEyes = source.numberOfEyes;
          this.name = source.name;
      }
      

      通过反射做这样的事情很快就会变得一团糟。你应该只复制属性吗?获取/设置方法呢?只读属性呢?这就是为什么最好在每个级别定义您实际想要的行为。然后,您可以根据需要在较低级别覆盖。

      // In Monkey class.
      public void CopyAttributes(Monkey source)
      {
          super.CopyAttributes(source);
          this.numberOfTails = source.numberOfTails;
      }
      

      【讨论】:

      • 你不能在接口中实现函数。
      • 我认为您可能需要重新考虑这一点。 IAnimal 是一个接口 - 没有实现。想想实现 IAnimal 的抽象类
      • 所以让它成为一个抽象类。或静态方法。哎呀。
      • 如果您这样做,那么您可以将其作为扩展方法添加到 IAnimal 接口并使用类型推断自动使其在所有派生类上工作(这是一个不错的技巧)。
      • @Dr Herbie See,出色的解决方案。从来没有想过!我对 -1 有点生气,因为它可以通过多种方式轻松纠正。我通常只对明显错误的答案给出 -1,而不是技术细节。
      【解决方案8】:

      尝试查看struct 方法

      【讨论】:

      • 这与问题有什么关系?
      猜你喜欢
      • 2020-03-29
      • 2016-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多