【问题标题】:Why does GetProperties list a protected property (declared in a generic base class) twice?为什么 GetProperties 两次列出受保护的属性(在通用基类中声明)?
【发布时间】:2014-07-10 12:23:25
【问题描述】:

当我声明以下简单类时:

class Class1<T>
{
    protected virtual T Prop1 { get; set; }
    protected virtual string Prop2 { get; set; }
}

class Class2 : Class1<string>
{
    protected override string Prop1 { get; set; }
    protected override string Prop2 { get; set; }
}

现在我使用反射来获取 Class2 的属性,如下所示:

var hProperties = typeof(Class2).GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);

那么 Prop2 将被列出一次,而 Prop1 将被列出两次!这种行为对我来说似乎很奇怪。 Prop1和Prop2不应该被视为相同吗??

如何在 hProperties 中只使用一次 Prop1?我不想使用 BindingFlags.DeclaredOnly,因为我还想获得 Class1 的其他未被覆盖的受保护属性。

【问题讨论】:

  • 我认为这与覆盖有关。 Class2 中Prop1 的签名与Prop1 中的签名不同。在您的示例中,根本没有理由覆盖 Prop1。
  • @Tony:你是对的,这里没有理由覆盖Prop1。我试着找到一个尽可能简单的例子。在我们的代码中,getter 和 setter 是不同的,因此覆盖属性是有意义的。
  • 这发生在哪个 .NET 版本上?我刚刚在 4.5.1 上进行了快速测试,得到了预期的两个结果。
  • @Jon:感谢您的测试。这发生在 .NET 版本 4.5
  • @Jon:现在我使用 .NET 版本 4.5.1 创建了一个控制台应用程序,但仍然有 3 个结果。奇怪的。我不知道我做错了什么。

标签: c# .net reflection


【解决方案1】:

让我们看看编译后的程序集的元数据,以确保这两个属性除了名称之外具有相同的结构:

我使用 ILDASM 而不是通常的反编译器工具来确保没有任何内容被隐藏或以更友好的方式显示。除了名称之外,这两个属性是相同的。

返回的两个Prop1 属性之一来自Class1,其中一个来自Class2。

这似乎是一个错误。该错误似乎是基类成员未正确添加到结果中。当DeclaredOnly 未指定时all inherited properties should be returned as well。

我正在使用 DotPeek 和 Reflector VS 扩展,它允许调试反编译的 BCL 代码来调试反射代码。这个问题中看到的行为是在这个方法中触发的:

    private void PopulateProperties(RuntimeType.RuntimeTypeCache.Filter filter, RuntimeType declaringType, Dictionary<string, List<RuntimePropertyInfo>> csPropertyInfos, bool[] usedSlots, ref RuntimeType.ListBuilder<RuntimePropertyInfo> list)
    {
      int token = RuntimeTypeHandle.GetToken(declaringType);
      if (MetadataToken.IsNullToken(token))
        return;
      MetadataEnumResult result;
      RuntimeTypeHandle.GetMetadataImport(declaringType).EnumProperties(token, out result);
      RuntimeModule module = RuntimeTypeHandle.GetModule(declaringType);
      int numVirtuals = RuntimeTypeHandle.GetNumVirtuals(declaringType);
      for (int index1 = 0; index1 < result.Length; ++index1)
      {
        int num = result[index1];
        if (filter.RequiresStringComparison())
        {
          if (ModuleHandle.ContainsPropertyMatchingHash(module, num, filter.GetHashToMatch()))
          {
            Utf8String name = declaringType.GetRuntimeModule().MetadataImport.GetName(num);
            if (!filter.Match(name))
              continue;
          }
          else
            continue;
        }
        bool isPrivate;
        RuntimePropertyInfo runtimePropertyInfo = new RuntimePropertyInfo(num, declaringType, this.m_runtimeTypeCache, out isPrivate);
        if (usedSlots != null)
        {
          if (!(declaringType != this.ReflectedType) || !isPrivate)
          {
            MethodInfo methodInfo = runtimePropertyInfo.GetGetMethod();
            if (methodInfo == (MethodInfo) null)
              methodInfo = runtimePropertyInfo.GetSetMethod();
            if (methodInfo != (MethodInfo) null)
            {
              int slot = RuntimeMethodHandle.GetSlot((IRuntimeMethodInfo) methodInfo);
              if (slot < numVirtuals)
              {
                if (!usedSlots[slot])
                  usedSlots[slot] = true;
                else
                  continue;
              }
            }
            if (csPropertyInfos != null)
            {
              string name = runtimePropertyInfo.Name;
              List<RuntimePropertyInfo> list1 = csPropertyInfos.GetValueOrDefault(name);
              if (list1 == null)
              {
                list1 = new List<RuntimePropertyInfo>(1);
                csPropertyInfos[name] = list1;
              }
              for (int index2 = 0; index2 < list1.Count; ++index2)
              {
                if (runtimePropertyInfo.EqualsSig(list1[index2]))
                {
                  list1 = (List<RuntimePropertyInfo>) null;
                  break;
                }
              }
              if (list1 != null)
                list1.Add(runtimePropertyInfo);
              else
                continue;
            }
            else
            {
              bool flag = false;
              for (int index2 = 0; index2 < list.Count; ++index2)
              {
                if (runtimePropertyInfo.EqualsSig(list[index2]))
                {
                  flag = true;
                  break;
                }
              }
              if (flag)
                continue;
            }
          }
          else
            continue;
        }
        list.Add(runtimePropertyInfo);
      }
    }

为什么公共属性的行为会消失?

      if (!(declaringType != this.ReflectedType) || !isPrivate)

有一个检查。

Class1&lt;string&gt;.Prop2 在这里被过滤掉了:

              bool flag = false;
              for (int index2 = 0; index2 < list.Count; ++index2)
              {
                if (runtimePropertyInfo.EqualsSig(list[index2]))
                {
                  flag = true;
                  break;
                }
              }
              if (flag)
                continue;

因为EqualsSig 返回真。如果您要求私人成员,似乎属性会按名称和 sig 进行重复数据删除......我不知道为什么。不过似乎是故意的。

遵循这个复杂的代码很累。 This is better and commented. 我怀疑他们正在删除私有属性,因为您可以通过从某个类继承来提升权限以获取它的所有私有成员。

答案如下:

// For backward compatibility, even if the vtable slots don't match, we will still treat
// a property as duplicate if the names and signatures match.

因此他们添加了一个 hack 以实现向后兼容性。

您必须添加自己的处理才能获得所需的行为。也许,Fastreflect 可以提供帮助。

【讨论】:

  • 我看不出这会是什么,除了一个错误。将所有属性更改为 public 并使用 BindingFlags.Public - 你只会得到两个结果。
  • 隐藏后是否算作继承属性?
  • 谢谢,这是一个非常好的答案!我已经在我们的代码中实现了一个解决方法。
【解决方案2】:

这是因为Class1&lt;T&gt;.Prop1 和Class2.Prop1 不是同一个属性...在Class1&lt;T&gt; 中,属性的类型为T,而在Class2 中,它的类型为string。

Class2 只是泛型类Class1&lt;T&gt; 的一种实现,但可能还有其他的……考虑一下:

class Class3 : Class1<int>
{
    protected override int Prop1 { get; set; }
    protected override string Prop2 { get; set; }
}

如果你认为Class2.Prop1与Class1&lt;T&gt;.Prop1具有相同的属性,Class3.Prop1与Class1&lt;T&gt;.Prop1具有相同的属性,那么Class2.Prop1也与Class3.Prop1相同,显然没有意义:一个是string 类型,另一个是int 类型...

【讨论】:

  • 当Class2 和Class3 不相关时,为什么Class2.Prop1 与Class3.Prop1 相同?
  • @Jon,这就是我的意思......它们不一样,但 OP 似乎希望 Class2.Prop1 被认为与 Class1&lt;T&gt;.Prop1 相同。 Class3 的例子只是为了表明它没有意义。
  • 从 usr 的回答来看......我认为你不能这么说。如果它们在类型上占用相同的 vtable 插槽,则源主要考虑属性“一个和相同”,虽然我完全不知道如何以及为什么在 CLR 级别实现这一点,但我没有立即看到原因@987654345无法为 @ 分配与 Class1&lt;string&gt;.Prop1_get 相同的插槽。还有令人困惑的反向兼容黑客,其目的不是很清楚......对我来说,“正确的”答案看起来一定很复杂,而且不完全是技术性的。
猜你喜欢
  • 2015-12-29
  • 2011-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多