C#使用reflection来复制基类属性
我想使用reflection来将MyObject的所有属性更新到另一个属性。 我遇到的问题是,特定的对象是从基类inheritance而来的,这些基类的属性值是不会更新的。
下面的代码复制顶级属性值。
public void Update(MyObject o) { MyObject copyObject = ... FieldInfo[] myObjectFields = o.GetType().GetFields( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); foreach (FieldInfo fi in myObjectFields) { fi.SetValue(copyObject, fi.GetValue(o)); } }
我正在查看是否有更多的BindingFlags属性,我可以用来帮助,但无济于事。
尝试这个:
public void Update(MyObject o) { MyObject copyObject = ... Type type = o.GetType(); while (type != null) { UpdateForType(type, o, copyObject); type = type.BaseType; } } private static void UpdateForType(Type type, MyObject source, MyObject destination) { FieldInfo[] myObjectFields = type.GetFields( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); foreach (FieldInfo fi in myObjectFields) { fi.SetValue(destination, fi.GetValue(source)); } }
我把它写成了一个扩展方法,可以用于不同的types。 我的问题是,我有一些模型绑定到asp mvc窗体,其他实体映射到数据库。 理想情况下,我只有1个类,但是实体是分阶段构build的,而asp mvc模型想要一次validation整个模型。
这里是代码:
public static class ObjectExt { public static T1 CopyFrom<T1, T2>(this T1 obj, T2 otherObject) where T1: class where T2: class { PropertyInfo[] srcFields = otherObject.GetType().GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); PropertyInfo[] destFields = obj.GetType().GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty); foreach (var property in srcFields) { var dest = destFields.FirstOrDefault(x => x.Name == property.Name); if (dest != null && dest.CanWrite) dest.SetValue(obj, property.GetValue(otherObject, null), null); } return obj; } }
嗯。 我认为GetFields
可以从链上获得成员,而且如果你不想inheritance成员,你必须明确地指定BindingFlags.DeclaredOnly
。 所以我做了一个快速testing,我是对的。
然后我注意到一些事情
我想使用reflection来将MyObject的所有属性更新到另一个属性 。 我遇到的问题是,特定的对象是从基类inheritance而来的,这些基类的属性值是不会更新的。
下面的代码复制顶级属性值。
public void Update(MyObject o) { MyObject copyObject = ... FieldInfo[] myObjectFields = o.GetType().GetFields( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
这将只获得字段 (包括这种types的私人领域),但不是属性 。 所以,如果你有这样的层次(请原谅!):
class L0 { public int f0; private int _p0; public int p0 { get { return _p0; } set { _p0 = value; } } } class L1 : L0 { public int f1; private int _p1; public int p1 { get { return _p1; } set { _p1 = value; } } } class L2 : L1 { public int f2; private int _p2; public int p2 { get { return _p2; } set { _p2 = value; } } }
那么L2
上的.GetFields
和你指定的BindingFlags
会得到f0
, f1
, f2
和_p2
,而不是p0
或p1
(属性,不是字段)或者_p0
或_p1
(它们是基类的私有的, L2
types的对象没有这些字段。
如果你想复制属性,请尝试做你正在做的,但使用.GetProperties
。
这不考虑具有参数的属性,也不考虑可能无法访问的私有get / set访问器,也不考虑只读枚举,所以这里有一个扩展的解决scheme?
我试图转换到C#,但通常的来源,没有这样做,我没有时间自己转换它。
''' <summary> ''' Import the properties that match by name in the source to the target.</summary> ''' <param name="target">Object to import the properties into.</param> ''' <param name="source">Object to import the properties from.</param> ''' <returns> ''' True, if the import can without exception; otherwise, False.</returns> <System.Runtime.CompilerServices.Extension()> Public Function Import(target As Object, source As Object) As Boolean Dim targetProperties As IEnumerable(Of Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)) = (From aPropertyInfo In source.GetType().GetProperties(Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) Let propertyAccessors = aPropertyInfo.GetAccessors(True) Let propertyMethods = aPropertyInfo.PropertyType.GetMethods() Let addMethod = (From aMethodInfo In propertyMethods Where aMethodInfo.Name = "Add" AndAlso aMethodInfo.GetParameters().Length = 1 Select aMethodInfo).FirstOrDefault() Where aPropertyInfo.CanRead AndAlso aPropertyInfo.GetIndexParameters().Length = 0 _ AndAlso (aPropertyInfo.CanWrite OrElse addMethod IsNot Nothing) _ AndAlso (From aMethodInfo In propertyAccessors Where aMethodInfo.IsPrivate _ OrElse (aMethodInfo.Name.StartsWith("get_") OrElse aMethodInfo.Name.StartsWith("set_"))).FirstOrDefault() IsNot Nothing Select New Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)(aPropertyInfo, addMethod)) ' No properties to import into. If targetProperties.Count() = 0 Then Return True Dim sourceProperties As IEnumerable(Of Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)) = (From aPropertyInfo In source.GetType().GetProperties(Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance) Let propertyAccessors = aPropertyInfo.GetAccessors(True) Let propertyMethods = aPropertyInfo.PropertyType.GetMethods() Let addMethod = (From aMethodInfo In propertyMethods Where aMethodInfo.Name = "Add" AndAlso aMethodInfo.GetParameters().Length = 1 Select aMethodInfo).FirstOrDefault() Where aPropertyInfo.CanRead AndAlso aPropertyInfo.GetIndexParameters().Length = 0 _ AndAlso (aPropertyInfo.CanWrite OrElse addMethod IsNot Nothing) _ AndAlso (From aMethodInfo In propertyAccessors Where aMethodInfo.IsPrivate _ OrElse (aMethodInfo.Name.StartsWith("get_") OrElse aMethodInfo.Name.StartsWith("set_"))).FirstOrDefault() IsNot Nothing Select New Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo)(aPropertyInfo, addMethod)) ' No properties to import. If sourceProperties.Count() = 0 Then Return True Try Dim currentPropertyInfo As Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo) Dim matchingPropertyInfo As Tuple(Of Reflection.PropertyInfo, Reflection.MethodInfo) ' Copy the properties from the source to the target, that match by name. For Each currentPropertyInfo In sourceProperties matchingPropertyInfo = (From aPropertyInfo In targetProperties Where aPropertyInfo.Item1.Name = currentPropertyInfo.Item1.Name).FirstOrDefault() ' If a property matches in the target, then copy the value from the source to the target. If matchingPropertyInfo IsNot Nothing Then If matchingPropertyInfo.Item1.CanWrite Then matchingPropertyInfo.Item1.SetValue(target, matchingPropertyInfo.Item1.GetValue(source, Nothing), Nothing) ElseIf matchingPropertyInfo.Item2 IsNot Nothing Then Dim isEnumerable As IEnumerable = TryCast(currentPropertyInfo.Item1.GetValue(source, Nothing), IEnumerable) If isEnumerable Is Nothing Then Continue For ' Invoke the Add method for each object in this property collection. For Each currentObject As Object In isEnumerable matchingPropertyInfo.Item2.Invoke(matchingPropertyInfo.Item1.GetValue(target, Nothing), New Object() {currentObject}) Next End If End If Next Catch ex As Exception Return False End Try Return True End Function
波格丹Litescu的解决scheme很好,但我也会检查你是否可以写入财产。
foreach (var property in srcFields) { var dest = destFields.FirstOrDefault(x => x.Name == property.Name); if (dest != null) if (dest.CanWrite) dest.SetValue(obj, property.GetValue(otherObject, null), null); }