简体   繁体   English

为什么在设置只读的覆盖属性时没有编译器错误?

[英]Why is there no compiler error when setting an overridden property that is read-only?

I've been learning about C# inheritance and interfaces ( 1 , 2 , 3 ).我一直在学习 C# 继承和接口( 123 )。 In the following example, why is there not a compiler error when the property is set in a derived class that makes the property read-only via the auto properties?在下面的示例中,为什么在派生类中设置属性并通过 auto 属性使属性只读时不会出现编译器错误?

Fiddle小提琴

using System;
        
public interface IFoo {
    string Title { get; set; }  
}

public abstract class AbstractFoo : IFoo {
    public virtual string Title { get; set; }   
}

public class Foo : AbstractFoo {
    public string Title { get; set; }   
}

public class Bar : AbstractFoo {
    public override string Title { get { return "Read Only Title"; } } 
}

public class Program
{
    public static void Main()
    {
        var foo = new Foo();
        Console.WriteLine(foo.Title);
        
        var bar = new Bar();
        Console.WriteLine(bar.Title); // output: "Read Only Title"
        
        bar.Title = "A new and better title!"; // Why does this not generate an error?
        Console.WriteLine(bar.Title); // output: "Read Only Title" - value was not set
    }
}

The underlying question, is it possible to prevent bar.Title from being set , ie a read-only field for that derived class only ?潜在的问题是,是否可以防止bar.Titleset ,即仅该派生类的只读字段 I see in other related answers that the new keyword can be used to re-declare the property in the derived class, but at the same time advising against it.我在其他相关答案中看到new关键字可用于重新声明派生类中的属性,但同时建议不要使用它。

You didn't override it making it read-only.您没有覆盖它使其只读。 You just only override the get , and left the set as the base implementation.您只需覆盖get ,并将set作为基本实现。 If you wanted to make it angry:如果你想让它生气:

public new string Title => "Read Only Title";

now it knows about a problem.现在它知道一个问题。

It seems you didn't understand how interfaces, implementations and redefines (new) works.您似乎不了解接口、实现和重新定义(新)的工作原理。

So, shortly, you can't change the property to make it readonly.因此,很快,您无法更改该属性以使其成为只读。

Using the "new" keyword, you can force the object of type Bar to have a readonly Title property.使用“new”关键字,您可以强制 Bar 类型的对象具有只读的 Title 属性。 But, what if someone casts the object to the interface?但是,如果有人将对象强制转换为接口呢? In this scenario, the Title property is the one of the interface (Read/Write) and not the one of the Bar class (ReadOnly).在这种情况下,Title 属性是接口(读/写)之一,而不是 Bar 类(只读)。

Just for explain it with an example.只是为了用一个例子来解释它。 Suppose you change your Bar code in this way:假设您以这种方式更改条形码:

public class Bar : AbstractFoo
{
    public new string Title { get { return "Read Only Title"; } }
}

In this way you're saying that the Bar class has a property that will overwrite the Title property of the AbstractFoo class.通过这种方式,您是说 Bar 类具有一个属性,该属性将覆盖 AbstractFoo 类的 Title 属性。

But, look at this code:但是,看看这段代码:

        var bar = new Bar();
        bar.Title = "John"; // Compile error: Bar Title is readonly

        var fooInterface = bar as IFoo;
        fooInterface.Title = "Doe"; // OK -> IFoo interface Title is writable
        Console.WriteLine(fooInterface.Title); // Doe -> Title is the one of the interface, not of the Bar class

As you can see:如你看到的:

  • If you use the Bar class, you can't write the Title property (what you want)如果使用 Bar 类,则不能编写 Title 属性(您想要的)
  • If you cast the object to the interface, it works with the AbstractFoo property... I'm not sure you want that如果您将对象强制转换为接口,它可以与 AbstractFoo 属性一起使用...我不确定您是否想要

I hope it's clear我希望很清楚

A property in .NET is effectively a combination of a get method and a set method. .NET 中的属性实际上是get方法和set方法的组合。 Although the two methods would be strongly associated with each other, there are times when it may make sense to override just one (more typically the "set" method).尽管这两种方法彼此密切相关,但有时只覆盖一个方法(更典型的是“set”方法)可能是有意义的。 For example, a base "observable object" class may have a get and set method that simply read or write a private field, but a derived class might usefully raise an event from the setter without changing the getter behavior.例如,一个基本的“可观察对象”类可能有一个 get 和 set 方法,可以简单地读取或写入一个私有字段,但派生类可能会在不改变 getter 行为的情况下从 setter 中有效地引发事件。

Even though changing a getter without changing a setter would be unusual, it may make sense in situations where a class has a properties to indicate whether its value can be modified via the reference, can be guaranteed never to change, or neither, and where the base class doesn't allow modification but doesn't guarantee immutability.尽管在不更改 setter 的情况下更改 getter 是不寻常的,但在类具有指示其值是否可以通过引用修改、可以保证永远不会更改或两者都不更改的情况下,以及基类不允许修改但不保证不变性。 In such cases, the default setter would throw a NotSupportedException ;在这种情况下,默认设置器会抛出NotSupportedException a derived class might plausibly use a specialized getter that eg lazily creates its value, but still use the default setter.派生类可能合理地使用专门的 getter,例如懒惰地创建其值,但仍使用默认的 setter。

All depends from what you need to do.一切都取决于您需要做什么。

Usually people define interfaces for write a kind of contract that they have to respect.通常人们定义接口来编写一种他们必须遵守的契约。 In this way you can write code that will work with any type of object that subscribe that contract (interface).通过这种方式,您可以编写适用于订阅该合约(接口)的任何类型的对象的代码。

Looking at your example, I don't understand why you create an interface with read/write access and then you ask to use it as readonly.看看你的例子,我不明白你为什么创建一个具有读/写访问权限的接口,然后你要求将它用作只读。

In any way, you have three options:无论如何,您有三个选择:

  • Change the Bar class for have the Title property with get/set method but throw an exception when someone sets the value (as supercat told before)使用 get/set 方法更改 Bar 类的 Title 属性,但在有人设置值时抛出异常(如 supercat 之前所说)
  • Change the Bar class as I wrote before, but pay attention to the situation where you cast the object to the interface (as I explained before)把Bar类改成我之前写的,但是要注意把对象投到界面上的情况(我之前解释过)
  • Separeate the readonly and writeable interfaces分离只读和可写接口

Here the code for the last options:这里是最后一个选项的代码:

/// <summary>
/// Readeable Foo
/// </summary>
public interface IReadonlyFoo : ICommonFoo
{
    string Title { get; }
}


/// <summary>
/// Readeable and writeable Foo
/// </summary>
public interface IFoo : ICommonFoo
{
    string Title { get; set; }
}


/// <summary>
/// Common interface between readonly and complete Foo
/// </summary>
public interface ICommonFoo
{
    string Name { get; set; }
}



/// <summary>
/// Abstract implementation of common properties/functions
/// </summary>
public abstract class AbstractCommonFoo : ICommonFoo
{
    public string Name { get; set; }
}


/// <summary>
/// Abstract implementation of the writeable Foo
/// </summary>
public abstract class AbstractFoo : AbstractCommonFoo, IFoo
{

    public string Title { get; set; }
}



/// <summary>
/// Abstrac implementation of the readonly Foo
/// </summary>
public abstract class AbstractReadonlyFoo : AbstractCommonFoo, IReadonlyFoo
{
    public string Title { get; }
}


/// <summary>
/// Foo
/// </summary>
public class Foo : AbstractFoo
{

}


/// <summary>
/// Readonly Foo (Bar)
/// </summary>
public class Bar : AbstractReadonlyFoo
{

}

If you really need to have both readonly and writeable situations, maybe this is the best for you.如果你真的需要同时拥有只读和可写两种情况,也许这对你来说是最好的。 So in your code, any method will accept:所以在你的代码中,任何方法都会接受:

  • ICommonFoo when Title is not needed (so you can pass both Foo and Bar class) ICommonFoo 当不需要 Title 时(因此您可以同时传递 Foo 和 Bar 类)
  • IReadonlyFoo when Title is only read (you can pass both Foo and Bar class)仅读取 Title 时的 IReadonlyFoo(您可以同时传递 Foo 和 Bar 类)
  • IFoo when you need to set the Title (you can pass only Foo) IFoo 当您需要设置标题时(您只能传递 Foo)

Using the two interfaces make your code longer, but easier to use.使用这两个接口会使您的代码更长,但更易于使用。 For example, in method where you need to set the Title, you put in parameters the IFoo interface, so if someone tries to pass a Bar, the compiler show the error.例如,在需要设置 Title 的方法中,您将参数放入 IFoo 接口,因此如果有人尝试传递 Bar,编译器会显示错误。 If you use the "throw NotImplementedException" (one of the other solutions), your code will compile, but will fail at runtime..如果您使用“throw NotImplementedException”(其他解决方案之一),您的代码将编译,但在运行时会失败。

It's not a simple topic.这不是一个简单的话题。 I hope I explain it in a good way.我希望我以一种好的方式解释它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM