在C#中将对象复制到“this”对象

3

我有一组类的层次结构,需要能够从一个对象复制所有公共属性到另一个对象。
每个类都有一定数量的公共属性,可能与其他任何类不同

示例:

class Base
{
  // Common properties/methods...
  public void Copy<T>(T data) where T : Base
  {
     // ...
  }
}

class D1 : Base
{
  public int ID
  {
    get;
    set;
  }
}

class D2 : Base
{
  public string Name
  {
    get;
    set;
  }
}

我通过谷歌搜索了解到以下几种方法:

  • 使用反射
  • 生成IL代码
  • 序列化

它们都非常复杂,或者非常慢,有时两者兼而有之。
我错过了什么吗?还有其他方法可以访问原始的this指针吗?

编辑:
我来澄清一下。
T是调用类的类型。例如,如果由D1调用,则T将始终为D1。
泛型的原因是我无法确切知道T是什么。
我错过了什么吗?
我应该只使用Base data作为参数吗?


1
那么,你会如何将D1.ID复制到D2.Name呢? - Oded
看起来这可能会有所帮助,https://dev59.com/3HM_5IYBdhLWcg3w2XHw。 - Noldorin
@Oded:你不会的。从D1复制将始终发生在D1对象上。我只想要一个通用方法,能够将T对象复制到此类型的T对象中。这个方法应该由Base公开。 - the_drow
1
你可以将Base data作为参数使用,但这样只能复制在Base中定义的属性,如果你想要类型安全的代码,请参考我的回答,了解如何在不同的子类之间复制属性。 - Niels van der Rest
5个回答

6
您遗漏了一个重要的问题,即您要求编译器知道T可能是D1和D2类型之一,但您只说T是一个Base。由于此信息仅在运行时得知,它如何可能知道对象的属性甚至类型呢?即使您可以使用“foreach(PropertyInfo in this.Properties)”来查找这些属性的名称,它也会在运行时找到,因此与反射一样慢(它只是更美观的语法)。它不能知道哪些属性是常见的,直到它知道它正在处理哪些类型,而您已经说过“在运行时我不会告诉你”,所以答案是“好吧,我必须在运行时查看”,即反射。
其次,只是因为D1和D2可能都有名为Size的属性并不意味着它们是相同的属性(除非该属性存在于公共祖先中)。
例如,
ArtilleryAmmo.Shell和PecanNut.Shell。
AcmeCorp.Staff和GandolfTheWizard.Staff
California.State和MyBrokenEngine.State LoudSpeaker.Volume和MassiveCrater.Volume Cave.Bats和BasketballGame.Bats
等等。

2
您可以通过架构变更并使用“属性包(PropertyBag)”来存储每个类的属性以解决此问题。
属性包本质上是一个 Dictionary<string, object>,您可以给数据命名并将其添加到包中。缺点是所有内容都会被强制转换为 object,因此它不太类型安全,还存在大量装箱/拆箱操作。另外,作为名称的字符串在编译时不会得到检查,因此输入错误一直是一个威胁。
当您在类上定义属性时,可以在类的属性包中存储和检索该项。
public int MyProperty
{
    get
    {
        return (int)_propertyBag["MyProperty"];
    }
    set
    {
        if(_propertyBag.Keys.Contains("MyProperty"))
        {
          _propertyBag["MyProperty"] = value;
        }
        else
        {
          _propertyBag.Add("MyProperty", value);
        }
    }
}

现在,要聚合所有派生类的属性,您可以公开它们的“原始” PropertyBag 并遍历它。
就像我之前说的那样,PropertyBags 不是类型安全的,所以如果层次结构中有两个具有相同属性名称但不同类型的类,则会出现问题。
编辑:如果您关心性能,您需要以多种方式实现它,并对不同的实现进行性能测试--我不能诚实地说 PropertyBag 是否比使用反射更快。

1

Base类中的Copy方法只能访问在Base类中定义的属性。您可以复制这些属性。

但是,如果不使用反射等技术,你是无法复制从子类继承的属性的。即使使用了反射,您还需要了解不同子类之间属性映射的知识,例如将ID属性复制到Name属性。

因此,您需要为每个(允许的)子类转换编写单独的实现。

public interface IBaseCopier<TFrom, TTo> where TFrom : Base, TTo : Base
{
  void Copy(TFrom from, TTo to);
}

public class D1ToD2Copier : IBaseCopier<D1, D2>
{
  public void Copy(D1 from, D2 to)
  {
    // Copy properties from the D1 instance to the D2 instance.
  }
}

您可以在工厂类中注册所有的ICopier<TFrom, TTo>实现。该类将根据类型参数查找复制器的实现。如果某种类型组合没有复制器,即不支持转换,则工厂应该抛出异常。
public class CopierFactory
{
  public ICopier<TFrom, TTo> Create<TFrom, TTo>() where TFrom : Base, TTo : Base
  {
    // Look up the ICopier implementation for the given types.
  }
}

编辑

您可以使用MemberwiseClone方法创建对象的副本。

public class Base
{
  public static T Copy<T>(T data) where T : Base
  {
    return data.MemberwiseClone() as T;
  }
}

如果您需要更多对克隆过程的控制,可以实现ICloneable接口

注意:您应该意识到,不能将D1实例克隆为D2实例。这就像是把一只羊克隆成一匹马。


由于我只是从类型T复制到此处的T,所以是否可以仅使用副本构造函数? - the_drow
你们还是不明白。 问题是我不能这样做 = data; // typeof(data) == typeof(this),两者都继承自基类。 所以它会像这样 D1(D1 data) {} 问题是,我如何将数据赋值给this? - the_drow
我知道MemberwiseClone。但是我怎么把它赋值给this呢?在C ++中,我只需要执行*this = data; - the_drow
1
在C#类中,您不能将值分配给this。但是,在结构体中可以,参见此问题 - Niels van der Rest
请纠正我如果我错了,但是我认为你在试图像使用 C++ 一样使用 C#。因此,我建议不要仅仅为了能够赋值给 this 而使用结构体。结构体和类不同。这段代码是否可以实现从 D1 内部分配给 this 的相同效果:D1 data = new D1(); D1 data2 = data; - Niels van der Rest
显示剩余5条评论

1

我认为复制方法应该被派生类D1,D2继承,并且它们有责任将自己的属性复制到/从其他类型中。


1
这将会在子类之间创建很多耦合。 - Niels van der Rest

0
我会创建一个针对Base类的扩展方法,代码如下:
namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int CopyTo<T>(this Base source, ref T dest)
        {
          // Use reflection to cycle public properties and if you find equally named ones, copy them.
        }
    }   
}

然后你可以在你的对象中调用它,像这样:

  source.CopyTo<ClassType>(ref this);

我没有测试过,所以不确定它是否能像描述的那样正常工作。在我参与的一个大型项目中,我做了类似于将数据行转换为实体的操作。


这是一个公共方法,我在内部使用复制。 - the_drow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接