我有一个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
?也许是一个自动克隆或自动复制类的解决方案{{1}会写在一行吗?
答案 0 :(得分:6)
考虑使用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更复杂,更不可靠,不建议或准备好用于生产系统。
答案 1 :(得分:4)
为类层次结构实现此类事物的一种面向对象的方法是在基类中引入受保护的复制构造函数(尽管它仍然需要您编写所有赋值):
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;
}
答案 2 :(得分:0)
我会做这样的事情。使用带有私有构造函数的静态工厂方法,并在基类中定义复制构造函数。
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);
答案 3 :(得分:0)
是的,正如Bas所建议的,你可以选择Automapper但是还有另外一个选择是使用Otis ..我知道Automapper比Otis好,但它可以被视为一个选项..
检查出来: -
http://code.google.com/p/otis-lib/
正如在官方网站上留下的那样: -
Otis是.Net对象转换库,即对象的对象 映射器。
它会自动生成转换器程序集或类 将一种类型的实例转换为其他类型的实例。这些 可以使用属性或类型在元数据中描述转换 单独在XML源(文件,字符串,数据库)
Otis旨在用于解决一些常见的设计问题 实施任务,例如轻松实施支持 DTO类(更多),或转换业务域类型实例 表示层实例,但更一般地说,它可以使用 在任何需要不同类型之间转换的地方。
Otis无需手动实现类型转换器。
答案 4 :(得分:0)
鉴于您真正想要做的是为不同的服务序列化对象的不同部分,我会使用Composition而不是Inheritance。
所以你会:
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;
}