【问题标题】:Why does sealed override of a property in C# copy not overriden accessor from base type?为什么 C# 副本中属性的密封覆盖不会从基类型覆盖访问器?
【发布时间】:2019-09-03 16:56:55
【问题描述】:

在 C# 中,覆盖自动属性并仅提供一个访问器会通过 PropertyInfo“丢失”另一个访问器进行反射,即使它是在基类中定义的。

乍一看可能很奇怪,但仔细分析后似乎是reasonable。

但是,将 override 更改为 sealed override 也会更改此行为并允许获取所有访问者:

using System.Reflection;
using NUnit.Framework;

[TestFixture]
public class PropertySealedOverrideReflectionTests
{
    public class Base
    {
        public virtual object Override { get; set; }
        public virtual object SealedOverride { get; set; }
    }

    public class Derived : Base
    {
        public override object Override { set => base.Override = value; }
        public sealed override object SealedOverride { set => base.Override = value; }
    }

    [Test]
    public void Override()
    {
        PropertyInfo overrideProperty = typeof(Derived).GetProperty(nameof(Derived.Override));
        // ---
        // getter from base class is "invisible" here
        // ---
        Assert.False(overrideProperty.CanRead);
        Assert.Null(overrideProperty.GetMethod);
    }

    [Test]
    public void SealedOverride()
    {
        PropertyInfo sealedOverrideProperty = typeof(Derived).GetProperty(nameof(Derived.SealedOverride));
        // ---
        // after changing to "sealed override" getter is in place
        // ---
        Assert.True(sealedOverrideProperty.CanRead);
        Assert.NotNull(sealedOverrideProperty.GetMethod);
    }
}

在提供的场景中,编译器的类型会发生什么变化以执行sealed override?这种行为的原因是什么?

【问题讨论】:

  • 当名称相同时,您是否看到相同的行为?意思是基类中的Override,子类中的sealed override Override?在这种情况下,sealed 应该做的就是防止您在另一个子类中覆盖,就像 Derived 的子类将无法覆盖 SealedOverride
  • 抱歉没有收到您的问题。我以这种方式命名属性只是为了便于说明。
  • C# 编译器必须确保密封整个属性覆盖的唯一方法是密封get 和set 访问器。然后要实现这一点,它必须实际覆盖 get 访问器,即使您没有在覆盖中编写任何 getter。当然,C# 编译器只会在它自己“发明”的访问器中调用基本实现。通过反射,会显示该 get 访问器的存在。
  • @detective 谢谢,但从 C# 语言的角度来看,sealed override 应该做什么很清楚,我所指的问题和行为是通过反射从基类获取属性获取器的可访问性,请参阅单元测试从问题。

标签: c# .net reflection system.reflection


【解决方案1】:

编译器在提供的场景中更改类型以执行密封覆盖?这种行为的原因是什么?

因为诸如“virtual”和“sealed”(或 CLR 用语中的“final”)之类的属性适用于方法而不是属性,编译器密封属性的唯一方法是将其方法标记为已密封。但是,如果缺少一个或另一个 setter 和 getter 怎么办?编译器是否应该将基类型的方法标记为密封?

不,我认为显然不是。 :)

所以,为了让编译器有一个方法来标记为密封,它必须创建一个,即使你没有声明一个。

恕我直言,看看反射给你的信息以及代码实际编译成的内容是很有启发性的。这是一个基于您的场景的简单代码示例:

class Base
{
    public virtual object P1 { get; set; }
    public virtual object P2 { get; set; }
    public virtual object P3 { get; set; }
}

class Derived : Base
{
    public sealed override object P1 { set => base.P1 = value; }
    public override object P2 { set => base.P2 = value; }
}

即基类声明了三个虚拟属性,除了名称之外都相同。然后派生类覆盖其中两个虚拟属性,密封其中一个。

如果你看一下 Derived 中属性的反射返回的描述符对象之间的差异,你会注意到一些事情:

  • 即使我们没有为P1 声明一个getter,反射仍然返回一个,DeclaringType 属性返回Derived 类型。
  • 但是对于P2,反射不会返回一个getter(这与your earlier question有关)。
  • 对于P3,再次返回一个getter,但对于这个,DeclaringType 返回Base 类型。
  • 对于P1getter,MethodBase.Attributes包含MethodAttributes.Final,表示方法是密封的。这是编译器不能放在基类型上的属性(出于显而易见的原因),因此必须在派生类型中实现该方法,以便该属性有一些存在的地方。

最后,如果我们查看生成的代码,我们会发现,确实,编译器不仅为我们创建了这个方法,它实际上只是直接委托给了基类 getter:

.method public hidebysig specialname virtual final 
        instance object  get_P1() cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance object TestSO57762322VirtualProperty.Base::get_P1()
  IL_0006:  ret
} // end of method Derived::get_P1

【讨论】:

  • 备注:对于P3,你已经可以在PropertyInfo上看到.DeclaringType是Base,即和你问的类型不同(保存在.ReflectedType )。如果您不希望在您询问的课程中没有任何更改,您可以要求“仅声明”,例如通过BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public 到 GetProperty 的相关过载。对于P1,getter 和setter 的MethodBase 也有一个属性.IsFinal,所以你没有有检查.Attributes。
猜你喜欢
  • 2019-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-05
  • 1970-01-01
  • 2015-04-26
  • 1970-01-01
相关资源
最近更新 更多