【问题标题】:Creating a cloned copy of subclass from baseclass从基类创建子类的克隆副本
【发布时间】:2015-03-25 22:24:39
【问题描述】:

考虑这种情况:

public class Base
{
    public int i;
}

public class Sub : Base
{
    public void foo() { /* do stuff */}
}

然后我想,给定Base 的实例,获取Sub 的克隆实例(在这种情况下,i=17),以便我可以在子类中调用foo

Base b = new Base { i=17 };
Sub s = CloneAndUpcast(b);
s.foo();

但是,我怎样才能创建CloneAndUpcast

我认为应该可以使用反射递归地克隆所有Base-members 和属性。但相当多的工作。

谁有更好、更简洁的想法?

附言。我正在考虑使用它的场景是树状结构中的一组“简单”类(这里没有循环图或类似的),并且所有类都是简单的值持有者。计划是有一个愚蠢的层来保存所有值,然后是一组类似的类(子类),实际上包含一些价值持有者不应该知道的业务逻辑。一般不好的做法是的。我认为它适用于这种情况。

【问题讨论】:

  • 你不能完全按照你的要求去做。有很多原因,但我只想说,即使可以,这通常是一种不好的做法。如果您可以向我们提供您想要解决的更广泛的问题,我们可以帮助您找到正确的模式。
  • 这听起来像是一个特定的问题,表明一个更普遍的问题正在以错误的方式解决。旧鞋和玻璃瓶都不合适。见:weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/…
  • 我认为您尝试实现的目标可能表明存在设计问题。此外,由于ibase 类的私有,所以sub 无论如何都不能使用它,因此根据Base 的工作原理,不需要复制该字段。
  • @tobsen - i 旨在作为公共成员的示例。对不起。

标签: c# reflection


【解决方案1】:

您可以使用 AutoMapper 来避免编写复制构造函数的乏味。

public class MyClass : MyBase
{
    public MyClass(MyBase source)
    {
        Mapper.Map(source, this);
    }
}

您需要在应用程序启动时运行一次

Mapper.CreateMap<MyBase, MyClass>();

您可以从https://github.com/AutoMapper/AutoMapper下载AutoMapper

【讨论】:

  • 简洁而精确。这应该是问题的答案。
【解决方案2】:

这是您可以按照您的要求做某事的一种方法(在许多可能性中)。我不确定这是否非常漂亮,调试起来可能有点难看,但我认为它有效:

class BaseClass
{
    public int i { get; set; }

    public BaseClass Clone(BaseClass b)
    {
        BaseClass clone = new BaseClass();
        clone.i = b.i;
        return clone;
    }

}

class SubClass : BaseClass
{
    public int j { get; set; }

    public void foo() { Console.WriteLine("in SubClass with value of i = {0}", i.ToString()); }
}

class Program
{
    static void Main(string[] args)
    {
        BaseClass b1 = new BaseClass() { i = 17 };
        BaseClass b2 = new BaseClass() { i = 35 };

        SubClass sub1 = CloneAndUpcast<SubClass>(b1);
        SubClass sub2 = CloneAndUpcast<SubClass>(b2);

        sub1.foo();
        sub2.foo();
    }

    static T CloneAndUpcast<T>(BaseClass b) where T : BaseClass, new()
    {
        T clone = new T();

        var members = b.GetType().GetMembers(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
        for (int i = 0; i < members.Length; i++)
        {
            if (members[i].MemberType== MemberTypes.Property)
            {
                clone
                    .GetType()
                    .GetProperty(members[i].Name)
                    .SetValue(clone, b.GetType().GetProperty(members[i].Name).GetValue(b, null), null);
            }

        }
        return clone;
    }
}

基本上,正如您所建议的,您使用反射来遍历对象的属性(我将ij 设置为公共属性)并在克隆对象中相应地设置值。关键是使用泛型告诉 CloneAndUpcast 你正在处理什么类型。一旦你这样做了,它就非常简单了。

希望这会有所帮助。祝你好运!

【讨论】:

  • 一个静态泛型,其 T 由其参数定义。那很完美!我遇到了一个内部结构甚至不需要反射的情况;这是一个由超类定义的列表,其处理方式因子类而异,所以我要做的就是复制它。
【解决方案3】:

根据“四人帮”:“偏好组合胜于继承”,这是这样做的完美理由......

如果我们有一个看起来像这样的超类:

public class SuperClass : Person 

SuperClass 可以轻松地装饰 Person 类,添加 Person 类中没有的属性。 但是如果超类装饰只用于 GUI 会发生什么?例如,指示“已选择”的布尔值。我们仍然能够从列表中的数据库中获取所有人员,但是在尝试创建超类和合并数据库结果时遇到了麻烦。

    foreach( var person in myPersonList){
    var sc = new SuperClass();
    sc.Selected = false;
    sc=person;
 }

编译器抱怨,因为对于编译器来说,超类不是一个人,它是一个超类。填充 Person 子类的属性的唯一方法是迭代并设置每个属性……像这样。

    SuperClass.Name = Person.Name;
    SuperClass.Id = Person.ID;  

确实很乏味。但是有一个更好的方法......不要让超类继承自 Person

public class SuperClass{
  public Person ThisPerson {get;set;}
  public bool Selected {get;set;}
}

这给了我们“包含”超类现在包含一个 Person 类。

现在我们可以这样做了:

foreach(var person in MyPersonList){
   var sc = new Superclass();
   sc.Selected = false;
   sc.Person = person;
}

这个类的消费者现在必须像这样限定超类/人的属性...

forach(var sc in MySuperClassList){
  var selected = sc.Selected;
  var name = sc.Person.Name;
}

这样做的好处在于,将来您可以添加任何您想要的其他容器,并且不会影响任何其他容器。您还可以将超类变形为它包含的任何内容。如果每个包含的类都成为接口,那么这是进一步的一步。

【讨论】:

    【解决方案4】:

    好吧,既然b 不是Sub,我们不能将它“克隆”为一个。

    如果Base 具有构造函数和公共属性的适当组合,以让Sub 中的构造函数确保其基类因此具有与b 相同的状态,那么我们可以使用它。

    我想我会绕过整个事情。如果我们只关心s 在其基础中具有与b 相同的状态,并且它没有我们要关心的其他状态(否则我们必须将它传递给CloneAndUpcast 方法),那么我们还需要s 吗?

    静态方法可以使用Base,而我们可以只使用static public void foo(Base bc)。我们甚至可以将其定义为扩展方法static public void foo(this Base bc),然后将调用编码为b.foo()。这不会让我们做的唯一事情CloneAndUpcast() 让我们做的是访问受保护的成员。

    【讨论】:

      【解决方案5】:

      克隆是一种不好的做法,您的问题是其原因(子类克隆)。 一般来说,您应该只使用 copy cotrs 并让子类接受父类作为参数。

      公共基础(){}

      公共基础(基础 pSource){}

      公共子(){}

      public Sub(Base pSource, 其他参数...){}

      公共子(子 pSource){}

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多