C#:使用反射按名称获取 field/property/method 值(字符串或 uint)

C#: Get field/property/method value (which is either string or uint) by name using reflection

我正在尝试从一个有 5 年历史的开源项目的许多旧程序集中解析一些数据,并且大部分都可以正常工作,但代码非常冗长。

编辑:另外,我被 .Net Framework 3.5 困住了

有两个成员我感兴趣,他们的名字是 "Version""TargetVersion"

直到最近,"Version" 成员还是 string 以各种方式定义的。它现在被替换为 "ModVersion" 从程序集版本中获取的成员。一些例子:

public string Version => "1.4";

public static readonly string Version = "1.8.14";

public const string Version = "2.8";

public static Version ModVersion => typeof(MainClass).Assembly.GetName().Version;

TargetVersion成员只有两种形式:public static readonly uintpublic const uint

所以目前我得到了成员类型,然后为各种成员类型嵌套了 if .. else if ...,如下所示:

                    string dirty = string.Empty;

                    MemberTypes memberType = GetMemberType(mod, "Version");

                    if (memberType == MemberTypes.Property) {
                        Log.Info("It's a property");
                        dirty = mod
                            .GetProperty("Version", PUBLIC_STATIC)
                            .GetValue(mod, null)
                            .ToString();
                    } else if (memberType == MemberTypes.Field) {
                        Log.Info("It's a field");
                        dirty = mod
                            .GetField("Version", PUBLIC_STATIC)
                            .GetValue(mod)
                            .ToString();
                    } else if (memberType == MemberTypes.Method) {
                        Log.Info("It's a method");
                        dirty = mod
                            .GetMethod("Version", PUBLIC_STATIC)
                            .Invoke(null, null)
                            .ToString();
                    } else {
                        Log.Info("Version: Unsupported member type or not found");
                    }

然后我有一堆类似的代码来获取 "TargetVersion",这是一个 uint。现在我需要添加一些其他东西来获得新的 "ModVersion" 以防找不到 "Version"...

有什么方法可以减少重复?例如,是否可以使用泛型? (我还是 C# 的新手)以避免重复 string vs uint vs. Version 的代码?有没有办法减少处理不同成员类型的代码量?

我在 SO 的其他地方看到过类似的东西,但不知道如何使其适应我的用例:

MemberInfo info = type.GetField(memberName) as MemberInfo ??
    type.GetProperty(memberName) as MemberInfo;

我最终希望实现的是我可以指定类型和成员名称并只取回值。有点像 bool TryGetMemberValue<T>(Type thing, string memberName, out <T>value) 方法。

希望这个问题不会太蠢,我还在学习基础知识:o

你快到了,也许是这样的

注意 : 我刚刚返回了一个字符串。如果你愿意,你可以传递另一个通用参数并转换结果,但是我假设你想要使用的所有类型都会覆盖 ToString

public static bool TryGetValue<T>(T instance, string name, out string value)
{
   value = default;

   var member = typeof(T).GetMember(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                         .FirstOrDefault();

   if (member == null)return false;

   switch (member.MemberType)
   {
      case MemberTypes.Field:
         value = (member as FieldInfo)?.GetValue(instance).ToString();
         break;
      case MemberTypes.Method:
         value = (member as MethodInfo)?.Invoke(instance, null).ToString();
         break;
      case MemberTypes.Property:
         value = (member as PropertyInfo)?.GetValue(instance).ToString();
         break;
      default:
         return false;
   }

   return true;

}

或者如果你真的想要 type 显式

public static bool TryGetValue<T,T2>(T instance, string name, out string value)
{
   ...

   switch (member.MemberType)
   {
      case MemberTypes.Field:
         value = (T2)(member as FieldInfo)?.GetValue(instance);
      ...
}

Full Demo Here

更新

这是一个你给出类型的版本,它启动一个实例来获取实例成员,然后在需要时进行处理

public static T GetValue<T>(object instance , MemberInfo member)
{
   switch (member.MemberType)
   {
      case MemberTypes.Field: return (T)(member as FieldInfo)?.GetValue(instance);
      case MemberTypes.Method: return (T)(member as MethodInfo)?.Invoke(instance, null);
      case MemberTypes.Property: return (T)(member as PropertyInfo)?.GetValue(instance);
      default:return default;
   }
}
 
public static bool TryGetValue<T>(Assembly asm, string className, string name, out T value)
{
   value = default;    
   var type = asm.GetType(className);    
   var instance = Activator.CreateInstance(type);

   try
   {
      var member = type.GetMember(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                       .FirstOrDefault();

      if (instance == null || member == null) return false;

      value = GetValue<T>(instance, member);
   }
   finally
   {
      (instance as IDisposable)?.Dispose();
   }

   return true;   
}

用法

Assembly asm = <some assembly>;

if(TryGetValue<uint>(asm, "MyNameSpace.Test1", "TargetVersion", out var out1))
     Console.WriteLine(out1);