如何将一个对象“克隆”成为一个子类对象?

29

我有一个类 A 和一个继承自类 A 并添加了一些字段的类 B

如果有一个类型为 A 的对象 a,如何创建一个包含对象 a 所有数据的类型为 B 的对象 b

我尝试过 a.MemberwiseClone() 但这只会给我另一个类型为 A 的对象。而且我不能将 A 强制转换为 B,因为继承关系只允许相反的转换。

正确的方法是什么?


谢谢您的回答。我一直在寻找一种自动化的方法,但您的建议是没有这样的方法。 :( - Vizu
1
很遗憾,不行。您需要添加构造函数或某种工厂方法。 - Reed Copsey
@Vizu 你采用了哪种方法?我也想要类似的东西,如果你有,请在这里分享。 - Arun Prasad E S
1
@ARUN 我按照被接受的答案所建议的做了,但我也会尝试一下得票最高的答案。 - Vizu
10个回答

11

语言本身没有内置自动实现这种操作的方式......

一个选项是向类B添加一个接受类A作为参数的构造函数。

然后你可以这样做:

B newB = new B(myA);
构造函数可以根据需要将相关数据进行复制。

能否将一个子类复制到具有一些相似字段的超类中? - Arun Prasad E S

11

我会给A类添加一个复制构造函数,然后在B类中添加一个新的构造函数,该构造函数接受A类的实例,并将其传递给基类的复制构造函数。


2
被接受的答案是我一直以来所做的,但这种变化简单而优雅。 - JMD

5
您可以使用反射来实现此功能。 优点:可维护性高。无需更改复制构造函数或添加或删除属性。 缺点:性能较慢。使用反射会降低程序的速度,但对于平均大小的类而言,仍然能够保持毫秒级别的运行速度。
下面是一个基于反射实现浅拷贝的示例,还支持子类复制,使用扩展方法:
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(objOut, property.GetValue(objIn, null), null);
        }
        catch (ArgumentException) { } // For Get-only-properties
    }
    foreach (FieldInfo field in fields)
    {
        field.SetValue(objOut, field.GetValue(objIn));
    }
    return objOut;
}

这种方法会复制所有属性-私有和公共,以及所有字段。属性是按引用复制的,因此它是浅复制。

单元测试:

[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 {}

什么被认为是平均班级规模? - Adam L. S.
@adam-l-s 哈哈,好问题。对于性能问题,我的回答一如既往:测量。如果速度足够快,那就使用它。虽然反射被认为比正常访问属性慢1000倍:https://dev59.com/WnVD5IYBdhLWcg3wTJvF - Nilzor
1
我知道这是一个六年前的答案,但代码中有一个错误。这一行代码: property.SetValue(objIn, property.GetValue(objIn, null), null); 应该改为: property.SetValue(objOut, property.GetValue(objIn, null), null); 否则属性只会被设置回原始对象而不是新对象。只是想帮助一些复制粘贴者。 - briandunnington
我注意到,如果你熟悉使用Reflection.Emit来生成一个按需克隆方法,性能问题可以完全消除。 - Dai

4

使用工厂方法模式

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

        public abstract A CreateInstance();

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

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

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

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

    private static void Main(string[] args)
    {
        var b = new B() { P1 = 111, P2 = 222 };

        var c = b.Clone();
    }

1
在B中创建一个ctor,允许传入类型为A的对象,然后复制A字段并根据需要设置B字段。

0
你可以在B类上创建一个Convert方法,该方法接受基类作为参数。
public ClassB Convert(ClassA a)
{
   ClassB b = new ClassB();
   // Set the properties
   return b;
}

你也可以为ClassB编写一个构造函数,使其接受一个ClassA对象。


0

不行,你不能这样做。实现这个的一种方法是在类B上添加一个接受类型为B的参数的构造函数,并手动添加数据。

所以你可以像这样:

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

3
我不同意在A上添加一个返回B的Clone()方法,因为这会导致循环依赖。 - Matt Howells
我同意类B的构造函数,但是为什么需要CloneToB()方法呢? - Jon Cage
嗯,你说得对,我不应该包含那个方法。我之所以包含它,是因为发帖人提到了MemberWiseClone()。无论如何,这段代码不好,我会删除它。谢谢。 - Razzie

0

虽然没有人建议过这个方法(而且这并不适用于所有人),但应该说,如果您有从一开始就创建对象b的选项,请使用该选项,而不是创建对象a,然后将其复制到对象b。例如,想象一下您在同一个函数中,并且有以下代码:

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...

不必担心上一个被注释掉的步骤,只需从 b 开始设置值:

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

我知道并不是每个人都会喜欢上面的内容。我遇到过很多程序员,他们太专注于自己的代码,以至于没有意识到一个更简单的解决方案就在眼前。 :)


0
在你的基类中添加以下CreateObject虚方法...
    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;
    }

然后如果你想从超类创建一个真实的子类对象,只需调用

this.CreateObject<subclass>();

搞定了!


-1
这是一个适用于我自己的、使用构造函数的方法:
class ClassA():
    def __init__(self, **attrs):
        self.__dict__.update(**attrs)
        ...

b = ClassA(**other_class.__dict__)

同时也可以与继承一起使用

class Fields(AbstractType):
    def __init__(self, **attrs):
        self.__dict__.update(**attrs)

    my_date = Field(Date, required=True)
    text_field = Field(String, required=True)
    ...

class MyClass(InputObjectType, Fields):
    pass


class MyClassWithError(ObjectType, Fields):
    error = Field(String, required=True)


error_class = MyClassWithError(**my_class.__dict__)
error_class.error = "my error description"

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