自动实现的属性如何在 C# 接口中工作?

mir*_*n80 2 c#

如果我在 C# 类中声明自动实现的属性,则public string Property { get; set; }变为:

private string _property;

public string get_Property() {
    return _property;
}

public void set_Property(string value) {
    _property = value;
}
Run Code Online (Sandbox Code Playgroud)

其中包括一个私有字段string _property。

如果我创建一个接口,我可以使用自动实现的属性,如下所示:

string Property { get; set; }

为什么我可以在接口内声明自动实现的属性,但不能使用更长、更详细的语法来声明私有字段?我知道这样的定义:

接口仅包含方法、属性、事件或索引器的签名

接口中的私有字段与类中的私有字段生成方式不一样吗?它到底生成了吗?

Dai*_*Dai 7

(作为前言,是的,这个答案了解 C# 8.0 DIM(默认接口方法),并且我添加了注释来澄清 C# 8.0 中关于接口成员中允许的修饰符的相关更改所引入的一些歧义)


为什么我可以在接口中声明自动实现的属性

它们不是自动实现的,只是an 中属性声明的语法与 a或中interface自动实现属性定义的语法相同。classstruct

这:

interface IFoo
{
    String Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

意思是:“IFoo有一个名为public 1 的 String属性Bar,它有一个 getter 和一个 setter。”

这:

class Foo
{
    String Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

意思是:“Foo有一个名为 name 的私有[2] String属性Bar,它有一个 getter 和一个 setter,并且 getter 和 setter 都是由编译器自动生成的,并对隐藏的实例字段进行操作。


请注意,接口中使用的语法与 或 中实现使用的语法class无关struct。IFoo所以与上面相同...

interface IFoo
{
    String Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

...我们可以有:

// Using auto-implemented property:
class Foo2 : IFoo
{
    public String Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
// Using explicit backing field:
class Foo3 : IFoo
{
    private String bar;

    public String Bar
    {
        get { return this.bar; }
        set { this.bar = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)
// Using expression-body syntax with a backing field:
class Foo4 : IFoo
{
    private String bar;

    public String Bar
    {
        get => this.bar;
        set => this.bar = value;
    }
}
Run Code Online (Sandbox Code Playgroud)
// Using explicit interface implementation with a backing field:
class Foo5 : IFoo
{
    private String bar;

    String IFoo.Bar
    {
        get { return this.bar; }
        set { this.bar = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)
// You can also use explicit interface implementation with an auto-implemented property:
class Foo6 : IFoo
{
    String IFoo.Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
// However, if it's a getter-only property you won't be able to set a property value in the constructor - but you can initialize it inline:
interface IReadOnlyFoo
{
    String Bar { get; }
}

class Foo7 : IReadOnlyFoo
{
    String IReadOnlyFoo.Bar { get; } = "foo"; // ok
}

class Foo8 : IReadOnlyFoo
{
    public Foo8()
    {
        this.Bar = "foo"; // <-- Error. `Bar` is not a member of `this`.
        // You also can't cast `(IReadOnlyFoo)this` because `IReadOnlyFoo` does not contain a setter.
    }

    String IReadOnlyFoo.Bar { get; }
}
Run Code Online (Sandbox Code Playgroud)

接口中的私有字段与类中的私有字段生成方式不同吗

接口没有字段,接口只有虚拟方法(或者更确切地说:interface可以将其视为单个 vtable)。请注意,在内部:属性和事件本质上也是虚拟方法(另请注意,虽然在内部它们是虚拟调用,但实现的接口方法不会自动virtual(在 C# 意义上),因为接口实现的子类不能任意使用override任何接口成员[ 3]。

也不要与 C# 8.0 中的“默认接口实现”混淆,它更类似于扩展方法,而不是将接口视为类,因为接口仍然不能有字段。

您还应该熟悉 C# 的表达式主体成员语法:

class Foo2
{
    String bar; // this is a private instance field

    String Bar // this is a private instance property
    {
        get => this.bar;
        set => this.bar = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

自 C# 8.0 起,interface现在允许对类型使用某些显式修饰符:例如,现在允许对接口成员使用publicand virtual(或) 修饰符。sealed您只能在 DIM 成员上使用privateand/or修饰符,这意味着下面的声明都是等效的:sealedBar

interface IFoo
{
    String Bar { get; set; }

    abstract String Bar { get; set; }
    
    public String Bar { get; set; }

    public abstract String Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

...但是这些Bar属性都不会导致String字段存在。根据 DIM 成员的 C# 8.0 规范(重点是我的):

接口可能不包含实例状态。虽然现在允许静态字段,但接口中不允许实例字段。接口中不支持实例自动属性,因为它们会隐式声明隐藏字段。

虽然在接口属性声明中允许使用public和,但除非该属性具有完全定义的(和/或)访问器主体,否则不能使用或,因为和/或修饰符指示该属性使用 DIM 方法(这意味着如果您希望 DIM 属性存储或加载这些主体需要引用同一中的其他属性或方法的数据,如下面的定义,它们都是等效的并产生相同的 IL+元数据输出:abstractprivatevirtual getsetprivatevirtualinterfaceOuterProperty

interface IFooWrapper
{
    IFoo Underlying { get; set; } // Implicitly `public abstract` btw

    public String OuterProperty
    {
        get { return this.Underlying.Bar; }
        set { this.Underlying.Bar = value; }
    }

    public String OuterProperty
    {
        get { return this.Underlying.Bar; }
        set { this.Underlying.Bar = value; }
    }

    public virtual String OuterProperty
    {
        get { return this.Underlying.Bar; }
        set { this.Underlying.Bar = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

1当然,忽略显式接口实现。
[2] 如果类成员没有显式访问修饰符,则默认情况下它们是私有的。
[3] 子类可以重新实现一个接口,该接口将具有覆盖对该接口成员的任何虚拟调用的效果,但前提是该成员是通过接口引用而不是通过对该类的超类型的对象引用访问的。

  • @miran80 `字符串栏 { get; 放; }` 语法自 C# 1.0 起就存在,因为它用于接口属性,但直到 C# 3.0 才支持类属性(作为自动实现的属性),并在 C# 4.0 中再次扩展。 (4认同)