带接口的 C# 接口实现 属性

C# interface implementation with an interface property

我开始更深入地研究 C# 编程,并且一直在研究接口。

我了解接口的基础知识,因为对于实现它们的任何 class,它们都应该是 "contracts"。接口中定义的任何内容都需要在从它们继承(不确定该术语是否正确)的任何 class 中实现。

所以我加了一个属性到一个本身就是接口的接口。当尝试实现父接口时,我在我的 class 中添加了一个 属性,这是一个 class,它实现了父接口中的 属性 接口。这个解释可能有点令人困惑,所以我在下面添加了一些代码。

interface IPropertyThatIsAnInterface
{
    public int X { get; set; }
}

class ClassThatImplementsIPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
    public int X { get; set; }
}
interface IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

class ClassThatImplementsIAnInterface : IAnInterface
{
    public ClassThatImplementsIPropertyThatIsAnInterface InterfaceImplmentationProperty { get; set; }
}

Visual Studio(在 .Net Core 3.0 [即 C# 8.0] 中)出现错误,指出我的 class、ClassThatImplementsIAnInterface 未实现接口 属性, public IPropertyThatIsAnInterface InterfaceProperty { get; set; }, (这是一个接口 属性)。这是C#的局限还是我对接口理解的局限?

我这样做的原因是因为我想为我创建的每个 ClassThatImplementsIAnInterface 实现定制的 IPropertyThatIsAnInterface。 IE。

class AnotherClassThatImplementsIPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
   public int X { get; set; }
   public int Z { get; set; }
}

我可以通过执行以下操作来解决这个问题

class ClassThatImplementsIAnInterface : IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

但问题在于,当我访问 属性 时,我需要在我的 class 定义中对 InterfaceProperty 进行每次调用,即 ((ClassThatImplementsIPropertyThatIsAnInterface)InterfaceProperty).Y 每当我访问一个自定义 class 属性。这还可以,但不是很好。

那么,这是 C# 的限制还是我理解的限制?

首先,属性 名称必须是 InterfaceProperty,而不是 InterfaceImplmentationProperty - 属性 名称必须匹配。

其次,是的,类型必须完全匹配。但是,这不会阻止您将 ClassThatImplementsIPropertyThatIsAnInterface 对象分配给 InterfaceProperty.

但是,如果您说在使用它之前必须进行转换,那么这意味着该界面不适合您正在做的事情。 interface 的全部要点是对象的使用者可以使用接口的功能,而不必知道它们是如何实现的。

此处正确的术语是您 "implement" 界面。使用接口是 "can do" 类型的关系。例如,实现 IDisposable "can be disposed". A class can implement several interfaces, and that is commonly done. For example, SqlConnection 的 class 同时实现 ICloneableIDisposable,这意味着它 "can be cloned" 和 "can be disposed".

但是,一个 class 只能 "inherit" 另一个 class(或者 abstract or concrete). This is a "is a" relationship. For example, SqlConnection "is a DbConnection”,正如您在声明中看到的那样:

public sealed class SqlConnection : System.Data.Common.DbConnection, ICloneable, IDisposable

这里有更多关于此的好读物:Interfaces (C# Programming Guide)

这里:Abstract and Sealed Classes and Class Members (C# Programming Guide)

这里:Classes (C# Programming Guide) - Class inheritance

这是我刚编的一个奇怪的例子:

interface ICanMakeNoise {
    void MakeNoise();
}

abstract class Animal {
    public abstract int NumberOfLegs { get; }
}

class Dog : Animal, ICanMakeNoise {

    public override int NumberOfLegs { get; } = 4;

    public void MakeNoise() {
        // Bark
    }
}

class Radio : ICanMakeNoise {
    public void MakeNoise() {
        // Play music
    }
}

那么我可以有这样的方法:

public void MakeThisMakeNoise(ICanMakeNoise noisyThing) {
    // This could be a dog or a radio, I don't have to know
    noisyThing.MakeNoise();
}

这是有效的 C#:

interface IAnInterface
{
    IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

interface IPropertyThatIsAnInterface
{
    int X { get; set; }
}

class ClassThatImplementsIAnInterface : IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

你可以这样做

class ClassThatImplementsIAnInterface : IAnInterface
{
    public ClassThatImplementsIPropertyThatIsAnInterface InterfaceImplmentationProperty { get; set; }

    public IPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

class ClassThatImplementsIPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
    public int X { get; set; }
}

请注意InterfaceImplmentationPropertyIAnInterface无关。

我们可以显式地实现InterfaceProperty,隐藏它:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty { get; set; }

然而,InterfacePropertyInterfaceImplmentationProperty 仍然是分开的。让我们将 InterfaceProperty 委托给 InterfaceImplmentationProperty:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty
{
    get => InterfaceImplmentationProperty;
    set => InterfaceImplmentationProperty = value; // ERROR
}

现在,我们有一个错误。因为,你看,IPropertyThatIsAnInterface 不一定是 InterfaceImplmentationProperty。但是,IAnInterface 承诺我可以在那里设置任何 IPropertyThatIsAnInterface

如果我们继续这条路,我们必须颠覆预期,并抛出异常:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty
{
    get => InterfaceImplmentationProperty;
    set => InterfaceImplmentationProperty = (ClassThatImplementsIPropertyThatIsAnInterface)value;
}

在这里,我添加了一个转换,这可能会在运行时失败。它可以很容易被忽略......我们可以更表达一点:

IPropertyThatIsAnInterface IAnInterface.InterfaceProperty
{
    get => InterfaceImplmentationProperty;
    set
    {
        if (!(value is ClassThatImplementsIPropertyThatIsAnInterface valueAsSpecificType))
        {
            throw new ArgumentException($"{nameof(value)} is not {typeof(ClassThatImplementsIPropertyThatIsAnInterface)}", nameof(value));
        }

        InterfaceImplmentationProperty = valueAsSpecificType;
    }
}

好的,我们在上面看到我们必须改变接口的契约才能使其工作。那么...让合同更灵活怎么样?

我们从使界面通用化开始:

interface IAnInterface<TPropertyThatIsAnInterface>
    where TPropertyThatIsAnInterface : IPropertyThatIsAnInterface
{
    TPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

这里只是按照您的命名风格。

那我们实现的时候就可以指定类型了:

class ClassThatImplementsIAnInterface : IAnInterface<ClassThatImplementsIPropertyThatIsAnInterface>
{
    public ClassThatImplementsIPropertyThatIsAnInterface InterfaceProperty { get; set; }
}

如果你真的想要丑陋的名字,你可以做显式实现和委托 属性 事情。


更进一步...界面的用途是什么?是不是这样您就可以通过接口处理对象而不必处理特定类型? - 理想情况下,您不必强制转换或检查类型。



来自评论:

I have implemented your suggestion but am having an issue when trying to populate a List<IFileProcessors<IProcessorParameters>>. I get a "cannot implicitly convert type" error.

看到了吗?您希望将它们视为同一类型。然后让他们成为同一类型。

好吧,有一个模式,就是有两个版本的界面,一个是通用的,另一个不是。然后泛型接口继承自非泛型。我会说,如果你能避免它,就避免它。如果有的话,它将导致在运行时进行更多类型检查。


理想情况下,您应该能够通过其接口处理该类型。界面应该够用了。因此不需要特定类型,因此也不需要使用它。

正如我在上面解释的那样 setter 是个问题。

如果 属性 的实际类型比界面上的类型更具体,那么界面会说 属性 允许 class 不允许的类型。

你能删除 setter 吗?

interface IAnInterface
{
    IPropertyThatIsAnInterface InterfaceProperty { get; }
}

interface IPropertyThatIsAnInterface
{
    int X { get; set; }
}

class ClassThatImplementsIAnInterface : IAnInterface
{
    public IPropertyThatIsAnInterface InterfaceProperty { get; }
}

ClassThatImplementsIAnInterface 仍然可以使用任何实现 IPropertyThatIsAnInterface 的类型来初始化 InterfaceProperty。消费者不必意识到这一点。而且,假设 IPropertyThatIsAnInterface 是有用的(它应该),应该不需要转换它来使用它。取决于消费者是否可以处理界面。在消费者需要特定类型的那一刻,您将进行铸造。