首页 > 解决方案 > 为什么 C# 副本中属性的密封覆盖不会从基类型覆盖访问器?

问题描述

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

乍一看可能很奇怪,但经过更详细的分析似乎是合理的。

但是,更改overridesealed 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在提供的场景中,编译器的类型会发生什么变化?这种行为的原因是什么?

标签: c#.netreflectionsystem.reflection

解决方案


在提供的场景中,编译器的类型会发生什么变化以进行密封覆盖?这种行为的原因是什么?

因为诸如“虚拟”和“密封”(或 CLR 用语中的“最终”)之类的属性适用于方法而不是属性,所以编译器密封属性的唯一方法是将其方法标记为密封。但是,如果缺少一个或另一个 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,你会注意到一些事情:

  • 即使我们没有为 声明一个 getter P1,反射仍然返回一个,DeclaringType属性返回Derived类型。
  • 但是对于P2,反射不会返回一个吸气剂(这与您之前的问题有关)。
  • 对于P3,再次返回一个 getter,但对于这个,DeclaringType返回Base类型。
  • 对于P1getter,MethodBase.AttributesincludesMethodAttributes.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

推荐阅读