【问题标题】:How to make a subclass constructor based on a parent class instance?如何根据父类实例制作子类构造函数?
【发布时间】:2014-08-11 03:58:01
【问题描述】:

我有一个 Item 和一个 AdvancedItem 子类(如果重要的话,全部由值类型组成):

public Item
{
    public string A;
    public bool B;
    public char C;
    ...// 20 fields
}

public AdvancedItem : Item
{
    public string Z;
}

很容易独立地创建一个 Item 或 AdvancedItem:

var item = new Item { A = "aa", B = true, C = 'c', ... };
var aItem = new AdvancedItem { A = "aa", B = true, C = 'c', ..., Z = "zz" };

现在,我只想通过单独提供字符串 Z 将 Item 转换为 AdvancedItem。为了实现这一点,我正在考虑使用构造函数。

尝试 A:

// annoying, we are not using the inheritance of AdvancedItem:Item
// so we will need to edit this whenever we change the class Item
public AdvancedItem(Item item, string z)
{
    A = item.A;
    B = item.B;
    ...;//many lines
    Z = z;
}

尝试 B:

// to use inheritance it seems I need another constructor to duplicate itself
public Item(Item item)
{
    A = item.A;
    B = item.B;
    ...;//many lines
}

public AdvancedItem(Item item, string z) : base(Item)
{
    Z = z;
}

有什么方法可以改进第二次尝试以避免编写多行 X = item.X 或许可以解决自动克隆或自动复制 public Item(Item item) 所在的类的方法写成一行?

【问题讨论】:

  • 我想知道你为什么要把一个项目变成一个高级项目。这似乎是一个奇怪的设计选择。
  • @mdebeus 事实上,在我的情况下,不同的序列化有不同的[DataMember()] 属性。我什至没有添加新字段,但我希望有不同的[DataMember()],具体取决于用于它的网络服务。也许还有另一种方法可以实现条件 DataMemberAttribute。

标签: c# inheritance constructor clone base-class


【解决方案1】:

考虑使用AutoMapper 在对象之间复制属性。

这将允许以下操作:

Item a = new Item { A = 3, B = 'a', .... };

AdvancedItem advanced= Mapper.Map<AdvancedItem>(a);
string z = "Hello World";
advanced.Z = z;

更新

如果您不想使用AutoMapper,您可以使用Reflection 或更好的Expressions。但是,这会使您的代码更加复杂

考虑这两种类型:

class Item
{
    public int A, B, C;
    public string D, E, F;
    private int privateInt;

    public Item(int valueOfPrivateField)
    {
        privateInt = valueOfPrivateField;
    }
}

class AdvancedItem : Item
{
    public string G;

    public AdvancedItem(int valueOfPrivateField) : base(valueOfPrivateField)
    {
    }
}

我们可以定义一个方法来创建一个按字段复制的表达式。既然您提到您的所有字段都是值类型,我们可以将每个字段一一复制到另一个对象:

    private static void MapFields<T>(T target, T source)
    {
        Type type = typeof (T);
        if (!Mappers.ContainsKey(type))
        {
            //build expression to copy fields from source to target;
            var targetParam = Expression.Parameter(typeof(object));
            var targetCasted = Expression.TypeAs(targetParam, typeof(T));
            var sourceParam = Expression.Parameter(typeof(object));
            var sourceCasted = Expression.TypeAs(sourceParam, typeof(T));
            var setters = new List<Expression>();
            //get all non-readonly fields
            foreach (var fieldInfo in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(f => !f.IsInitOnly))
            {
                Expression targetField = Expression.Field(targetCasted, fieldInfo);
                Expression sourceField = Expression.Field(sourceCasted, fieldInfo);
                setters.Add(Expression.Assign(targetField, sourceField));
            }
            Expression block = Expression.Block(setters);

            var mapperFunction = Expression.Lambda<Action<object, object>>(block, targetParam,
                sourceParam).Compile();
            Mappers[type] = mapperFunction;
        }
        Mappers[type](target, source);

    }

    private static readonly Dictionary<Type, Action<object, object>> Mappers =
        new Dictionary<Type, Action<object, object>>();

这缓存了将所有字段从源对象映射到目标对象的函数,并且应该具有与手动编写this.A = A, this.B = B等接近相同的性能。

调用方法:

    static void Main(string[] args)
    {
        var item = new Item(56) {A = 5, B = 6};

        var advanced = new AdvancedItem(0);

        MapFields(advanced, item);
        int a = advanced.A; //5
        int b = advanced.B; //6;
        //note that advanced.privateInt == 56!
    }

请注意,此代码比 AutoMapper 更复杂且可靠性更低,不推荐或不准备用于生产系统。

【讨论】:

  • 很好,但是对于我的库,我将无法包含 AutoMapper(尝试使用尽可能少的外部代码依赖,以使库尽可能小)。
  • 听起来并不比 AutoMapper 复杂:我在 AutoMapper 上运行 git diff --stat `git hash-object -t tree /dev/null` 并返回149053 插入 741 个文件。
  • 但比使用自动映射器更复杂;)
【解决方案2】:

为类层次结构实现这种事情的一种面向对象的方法是在基类中引入一个受保护的复制构造函数(尽管它仍然需要您编写所有赋值):

public class Item
{
    protected Item(Item other)
    {
        this.A = other.A;
        this.B = other.B;
        this.C = other.C;
    }

    public string A;
    public bool B;
    public char C;
    // 20 fields
}

然后你会像这样从派生类中调用它:

public class AdvancedItem : Item
{
    public AdvancedItem(Item item, string z): base(item)
    {
        Z = z;
    }

    public string Z;
}

请注意,这种方法不会阻止您编写所有的赋值行,但您只需要编写一次,这确实意味着您现在可以使用受保护的复制构造函数,这非常有用。此外,分配现在都在它们所属的基类中。

这种方法可以扩展到进一步的派生类。您可以向AdvancedItem 引入一个受保护的复制构造函数,按照公共构造函数编写(以避免重复代码)。

例如:

public class AdvancedItem : Item
{
    protected AdvancedItem(AdvancedItem other): this(other, other.Z)
    {
    }

    public AdvancedItem(Item item, string z): base(item)
    {
        Z = z;
    }

    public string Z;
}

public class EvenMoreAdvancedItem: AdvancedItem
{
    public EvenMoreAdvancedItem(AdvancedItem advancedItem, double q): base(advancedItem)
    {
        Q = q;
    }

    public double Q;
}

【讨论】:

  • +1 因为你提醒我保护复制构造函数
【解决方案3】:

我会做这样的事情。使用带有私有构造函数的静态工厂方法,并在基类中定义一个复制构造函数。

AdvancedItem
{
    Public Static AdvancedItem FromItem(Item i, string z)
    {
        AdvancedItem item = new AdvancedItem(i);
        item.Z = z;

        return item;
    }

    private AdvancedItem(Item i) : Base(i) {}
}

那么用法就是AdvancedItem i = AdvancedItem.FromItem(item, extraThing);

【讨论】:

    【解决方案4】:

    是的,正如 Bas 所建议的那样,您可以使用 Automapper,但除此之外,还有另一种选择是使用 Otis。我知道 Automapper 比 Otis 更好,但它可以被视为一种选择。

    检查一下:-

    http://code.google.com/p/otis-lib/

    如官方网站所言:-

    Otis 是一个 .Net 对象转换库,即对象到对象 映射器。

    它会自动生成转换器程序集或类 将一种类型的实例转换为另一种类型的实例。这些 可以使用属性在类型元数据中描述转换,或者 分别在 XML 源(文件、字符串、数据库)中

    Otis 旨在用于解决一些常见的设计和 实施任务,例如以简化支持的实施 DTO 类(更多在这里),或转换业务域类型实例 表示层实例,但更一般地说,它可以用于 任何需要在不同类型之间进行转换的地方。

    Otis 消除了手动实现类型转换器的需要。

    【讨论】:

      【解决方案5】:

      鉴于您真正想做的是为不同的服务序列化对象的不同部分,我会使用组合而不是继承。

      所以你会:

      public Item
      {
          public string A;
          public bool B;
          public char C;
          ...// 20 fields
      }
      
      public AdvancedItem
      {
          [DataMember]
          public Item baseItem;
          [DataMember]
          public string Z;
      }
      
      public Item(Item item)
      {
          A = item.A;
          B = item.B;
          ...;//many lines
      }
      
      public AdvancedItem(Item item, string z)
      {
          baseItem = item; // or item.Clone();
          Z = z;
      }
      

      【讨论】:

      • 通过组合,不再需要构造函数public Item(Item item)。但是在使用 DataContractSerializer 或 DataContractJsonSerializer 时,如何使用您的技术实现平面序列化?喜欢{"A":"aa","B":true,...,"Z":"zz"}?我不要{"baseItem":{"A":"aa","B":true,...},"Z":"zz"}
      • 为什么要关心序列化数据是否平坦。序列化程序旨在序列化/反序列化一个类,这最终是您将要使用的。您是否打算对序列化数据做一些事情而不是将其反序列化回对象?另外,我不会完全取消 Item(Item item) 构造函数。使用对象时,复制构造函数很有用。
      • 他们不想改变网络服务,所以我必须处理它(xml和json)。
      猜你喜欢
      • 1970-01-01
      • 2021-05-12
      • 2019-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-10
      • 1970-01-01
      相关资源
      最近更新 更多