【问题标题】:Getting the value of a tuple using reflection使用反射获取元组的值
【发布时间】:2019-12-27 08:52:12
【问题描述】:

所以我试图使用反射来获取元组的值,唯一的问题是我得到了一个异常:System.Reflection.TargetInvocationException。我已经尝试将它的价值作为这里建议的一条评论: Casting to a Tuple<object,object>, var itemX = t.GetProperty("ItemX").GetValue(data); 如果我使用 lem.FieldType.GetProperty("Item1").Name ,我可以将名称取回为 Item1, Item2 等...,我是正确使用它还是有任何其他方式?

FieldInfo[] fields = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
FieldInfo[] tuples = fields.Where(field=>typeof(IStructuralEquatable).IsAssignableFrom(field.FieldType) && typeof(IStructuralComparable).IsAssignableFrom(field.FieldType)).ToArray();

Debug.WriteLine(tuples.Length.ToString()" ->");
foreach (var elem in tuples)
{
    Debug.WriteLine(elem.FieldType.GetProperty("Item1").GetValue(this,null).ToString());

    PropertyInfo[] info = elem.FieldType.GetProperties();
    Debug.WriteLine(info[0].GetValue(this,null).ToString());
    for(int i=0;i<info.Length;i++)
    {
        Debug.WriteLine(info[i].GetValue(this,null).ToString());
    }

还有我的元组:

protected Tuple<string,int, int, int> testTuple = new Tuple<string, int, int, int>("Test",1,0,1);

【问题讨论】:

  • 你能发一个minimal reproducible example 以便更清楚到底是什么问题吗?另外,TargetInvocationException 包含一个更重要的 InnerException,您能检查一下并告诉我们它说什么吗?
  • tuple .GetType() .GetProperties() .Where(prop =&gt; Regex.IsMatch(prop.Name, "Item[0-9]+")) .ToDictionary(prop =&gt; prop.Name, prop =&gt; prop.GetValue(tuple)); 获取所有Item1..ItemN 属性名称和值
  • 小心.ToString() - 如果GetValue(this,null) 返回null怎么办?

标签: c# reflection tuples


【解决方案1】:

让我们查询 tupleItem1..ItemN 属性;我们可以借助 Linq正则表达式 来实现,例如

  using System.Linq;
  using System.Reflection;
  using System.Text.RegularExpressions;

  ...

  Dictionary<string, object> result = testTuple
    .GetType()
    .GetProperties()
    .Where(prop => prop.CanRead)
    .Where(prop => !prop.GetIndexParameters().Any())
    .Where(prop => Regex.IsMatch(prop.Name, "^Item[0-9]+$"))
    .ToDictionary(prop => prop.Name, prop => prop.GetValue(testTuple));

是时候将它包装到你的方法中了:

  ...
  foreach (var tuple in tuples) {
    var result = tuple
      .GetType()
      .GetProperties()
      .Where(prop => prop.CanRead)
      .Where(prop => !prop.GetIndexParameters().Any())
      .Where(prop => Regex.IsMatch(prop.Name, "^Item[0-9]+$"))
      .Select(prop => new {
         name  = prop.Name,
         value = prop.GetValue(tuple), 
       });

    foreach (var item in result)
      Debug.WriteLine($"{item.name} = {item.value}");
  }
  ...

编辑:让我们从获取Tuple&lt;,...,&gt; 类型的所有字段开始(参见下面的cmets):

  Object objectToInspect = this;

  HashSet<Type> typleTypes = new HashSet<Type>() {
    typeof(Tuple<>),
    typeof(Tuple<,>),
    typeof(Tuple<,,>),
    typeof(Tuple<,,,>),
    typeof(Tuple<,,,,>),
    typeof(Tuple<,,,,,>),
    typeof(Tuple<,,,,,,>),
    typeof(Tuple<,,,,,,,>),
  };

  var fieldsWithTuples = objectToInspect
    .GetType()
    .GetFields(BindingFlags.NonPublic |
               BindingFlags.Instance |
               BindingFlags.Public |
               BindingFlags.Static |
               BindingFlags.FlattenHierarchy)
    .Where(field => field.FieldType.IsGenericType)
    .Where(field => typleTypes.Contains(field.FieldType.GetGenericTypeDefinition()))
    .Select(field => new {
      name  = field.Name,
      value = field.GetValue(field.IsStatic
                 ? null                     // we should provide null for static
                 : objectToInspect)
    })
    .Where(item => item.value != null);
 // .Select(item => item.value) // if you want tuple values
 // .ToArray();                 // materialized as an array

现在我们可以使用我上面的代码了:

  foreach (var tuple in fieldsWithTuples.Select(f => f.value)) {
    var result = tuple
      .GetType()
      .GetProperties()
      .Where(prop => prop.CanRead)
      .Where(prop => !prop.GetIndexParameters().Any())
      .Where(prop => Regex.IsMatch(prop.Name, "^Item[0-9]+$"))
      .Select(prop => new {
         name  = prop.Name,
         value = prop.GetValue(tuple), 
       });

    foreach (var item in result)
      Debug.WriteLine($"{item.name} = {item.value}");
  }

【讨论】:

  • 嘿,谢谢你帮助我。我不得不将 tupe.GetType() 更改为 tuple.FieldType,但它仍然抛出异常:Error verifying System.Text.RegularExpressions.Regex,我添加了我的元组的样子
  • @porsekin:FieldInfo[] tuples = fields... 片段有问题。据我所知,您有一个对象,它的 fields 类型为 Tuple&lt;...&gt;,您想打印出来。是你的情况吗?
  • 是的,我可以将这些字段放入元组数组中。我想打印出类中每个元组的每个值。添加了我如何获得 fields 的代码。
  • 如果我删除.Where(prop =&gt; Regex.IsMatch(prop.Name, "^Item[0-9]+$")),我会得到一个System.MethodAccessException for value = prop.GetValue(tuple)
  • @porsekin:获取Tuple&lt;&gt;(以及Tuple&lt;,&gt;Tuple&lt;,,&gt;等)类型的所有好友并非易事;查看我的编辑
【解决方案2】:

元组定义如下:Tuple&lt;T1,T2,T3,T4,T5,T6,T7,TRest&gt;

按照元组中的值的数量,它的逻辑不一样 因此,要在所有情况下(即使有 8 个或更多项)使用反射读取元组,只需使用这段代码(通用编码):

    public IEnumerable<object> EnumerateValueTuple(object valueTuple)
    {
        var tuples = new Queue<object>();
        tuples.Enqueue(valueTuple);

        while (tuples.Count > 0 && tuples.Dequeue() is object tuple)
        {
            foreach (var field in tuple.GetType().GetFields())
            {
                if (field.Name == "Rest")
                    tuples.Enqueue(field.GetValue(tuple));
                else
                    yield return field.GetValue(tuple);
            }
        }
    }

你可以使用:

var item = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);

foreach(var value in EnumerateValueTuple(item))
  Console.Out.WriteLine(value); // Prints "1 2 3 4 5 6 7 8 9 10 11"

【讨论】:

  • 嘿,谢谢。但它适用于我不想使用的 ValueTuple 扩展。如果我尝试将它用于元组,我会得到 System.MethodAccessException。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-24
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多