使用反射访问已知类的某个未知字段

本文关键字:未知 字段 反射 访问 | 更新日期: 2023-09-27 18:18:25

假设我有以下类:

class Person
{
     public FirstType firstField ; // has a getter-setter  
     public SecondType secondField; // has a getter-setter  
     // more fields of other types
} 

我想构建一个函数,它接受Person类作为其第一个参数,并接受第一个参数字段之一的类型作为其第二个参数-假设Person的字段具有不同的类型。

我的目标是能够在第二个参数上使用反射,然后使用一些等效的点符号来访问传递的类。

public void SetPersonField(Person person, <should it be Type?> personFieldType)
{
   // accessing person's correct field type
} 

怎么做呢?

使用反射访问已知类的某个未知字段

通过反射可以很容易地做到这一点:你可以获得一个类型的所有公共属性,找到给定类型的一个,并在传递的person实例的上下文中设置其值。

我还建议使用泛型方法,以获得更好的类型安全性和更简洁的使用:

public static void SetPersonField<T>(Person person, T value)
{
    typeof (Person)
        .GetProperties()
        .Single(p => p.PropertyType == typeof(T))
        .SetValue(person, value);
}
// Usage (both are correct):
SetPersonField(person, FirstType.EnumValue);
SetPersonField<FirstType>(person, FirstType.EnumValue);

你甚至不需要指定泛型类型,因为它可以从输入参数中理解。

注意,如果没有这种类型的属性,或者如果有多个这种类型的属性,.Single将抛出异常。如果不能保证存在性或唯一性,则需要在SetPersonField方法中处理。
此外,要始终记住使用反射时的巨大性能下降。

更通用呢?

你可以实现一个更通用的方法,它将支持任何类型的属性和对象:

public static void SetField<TObject, TProperty>(TObject obj, TProperty value)
{
    typeof(TObject)
        .GetProperties()
        .Single(p => p.PropertyType == typeof(TProperty))
        .SetValue(obj, value);
}
var person = new Person();
SetField(person, 123); // finds the only int property and sets its value
SetField(person, FirstType.EnumValue); // find the only FirstType property
SetField<Person, DateTime?>(person, DateTime.Now); // finds the only DateTime? property

请注意,对于某些特定类型(例如,Nullable),您需要显式指定类型,因为无法从您的数据中理解。

我以前使用过这个类

Public Class PropertySetter
Public Shared Sub SetObjectProperty(ByRef obj As Object, propertyInfo As PropertyInfo, propertyValue As Object)
    'if we do not need to convert propertyValue to certain type
    If IsNothing(propertyValue) OrElse propertyInfo.PropertyType = propertyValue.GetType() Then
        obj.GetType().GetProperty(propertyInfo.Name).SetValue(obj, propertyValue)
    Else
        'Convert.ChangeType does not handle conversion to nullable types
        'if the property type is nullable, we need to get the underlying type of the property
        Dim targetType = If(IsNullableType(propertyInfo.PropertyType), Nullable.GetUnderlyingType(propertyInfo.PropertyType), propertyInfo.PropertyType)
        'convert Enum
        If (propertyValue.GetType() = GetType(String)) Then
            If propertyInfo.PropertyType.IsEnum OrElse (Not Nullable.GetUnderlyingType(propertyInfo.PropertyType) Is Nothing AndAlso Nullable.GetUnderlyingType(propertyInfo.PropertyType).IsEnum) Then
                'if propertyValue is equal to enum value
                Try
                    propertyValue = [Enum].Parse(targetType, propertyValue.ToString())
                Catch ex As Exception
                    'if propertyValue is equal to enum description 
                    Dim enumValues = targetType.GetFields()
                    For Each enumValue In enumValues
                        Dim descriptionAttribute = enumValue.GetCustomAttribute(Of DescriptionAttribute)()
                        If Not descriptionAttribute Is Nothing Then
                            If (descriptionAttribute.Description.Trim().ToUpper() = propertyValue.ToString().ToUpper()) Then
                                propertyValue = enumValue.GetValue(Nothing)
                                Exit For
                            End If
                        End If
                    Next
                End Try
            End If
        End If
        'set object property via reflection
        obj.GetType().GetProperty(propertyInfo.Name).SetValue(obj, Convert.ChangeType(propertyValue, targetType, CultureInfo.InvariantCulture))
    End If
End Sub
Private Shared Function IsNullableType(type As Type) As Boolean
    Return type.IsGenericType AndAlso type.GetGenericTypeDefinition().Equals(GetType(Nullable(Of )))
End Function
End Class

注:如你所见,它是vb.net。你可以使用这个服务来获取c#代码

为将来参考,下面是如何创建Getter查询:

public static object GetPersonField(Person person, Type fieldType)
{
    return typeof (Person)
            .GetProperties()
            .Single(p => p.PropertyType == fieldType)
            .GetValue(person);
}