我可以实现一个包含接口所需子类型属性的接口吗?

发布于 2024-10-15 18:17:15 字数 560 浏览 2 评论 0原文

我收到以下错误:

ClassName.PropertyName 无法实现 IClassType.PropertyName 因为它没有匹配的 IBasePropertyType 返回类型

现在,对于代码:

public class ClassName : IClassType
{
    public IChildPropertyType PropertyName { get; set; }
}

public interface IClassType
{
    public IBasePropertyType PropertyName { get; set; }
}

public interface IBasePropertyType
{
    // some methods
}

public interface IChildPropertyType : IBasePropertyType
{
    // some methods
}

有没有办法做我正在尝试的事情?我知道问题在于协变/逆变,但我似乎不知道如何做到这一点。

I am receiving the following error:

ClassName.PropertyName cannot implement IClassType.PropertyName
because it does not have the matching return type of IBasePropertyType

Now, for the code:

public class ClassName : IClassType
{
    public IChildPropertyType PropertyName { get; set; }
}

public interface IClassType
{
    public IBasePropertyType PropertyName { get; set; }
}

public interface IBasePropertyType
{
    // some methods
}

public interface IChildPropertyType : IBasePropertyType
{
    // some methods
}

Is there a way to do what I am attempting? I know that the issue is with co/contravariance, but I can't seem to figure out how to do this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

阳光下慵懒的猫 2024-10-22 18:17:15

为了实现给定的接口,您必须具有相同的返回类型。然而,有一些潜在的解决方法可以让生活变得更轻松:

  1. 使您的接口通用
  2. 并显式实现该接口。

如果您将 IClassType 设为泛型,如下所示:

public interface IClassType<T> where T : IBasePropertyType
{
    public T PropertyName { get; set; }
}

...那么您可以使用各种属性类型来实现此接口:

public class ClassName : IClassType<IChildPropertyType>
{
    public IChildPropertyType PropertyName { get; set; }
}

另一种选择是让您的接口保持非泛型,但要有一个泛型基类型显式实现接口:

public class ClassBase<T> : IClassType
    where T : IChildPropertyType
{
    IBasePropertyType IClassType.PropertyName { 
        get {return PropertyName;}
        set {PropertyName = (IChildPropertyType)value;}
    }
    T PropertyName {get;set;}
}

请注意,最后一个选项不太理想,因为您必须动态地将属性转换为给定的子类型:虽然您可以保证每个 IChildProperty 类型都是 IBasePropertyType,但不能保证每个 IBasePropertyType 都是 IChildPropertyType。但是,如果您可以从原始接口中消除 setter,或者可以采取其他步骤来保证代码中永远不会使用错误的类型调用 setter,那么这可能会起作用。

更新

这不会从根本上改变答案,但 C# 现在支持协变和逆变接口。例如,如果您只希望接口公开属性上的 getter,则可以将该类型标记为 out 类型:

public interface IClassType<out T>
{
    public T PropertyName { get; }
}

实现类型不必更改任何内容 - 它们仍然可以实现如果他们愿意,则设置器:

public class ClassName : IClassType<IChildPropertyType>
{
    public IChildPropertyType PropertyName { get; set; }
}

然后,即使您的类实现了 IClassType,它仍然可以转换IClassType并用作针对该接口编写的任何方法的参数:

    IClassType<IBasePropertyType> c = new ClassName();
    IBasePropertyType prop = c.PropertyName;

In order to implement the given interface, you must have the same return type. However, there are a couple of potential work-arounds to make life easier:

  1. make your interface generic
  2. implement the interface explicitly.

If you make IClassType generic, like so:

public interface IClassType<T> where T : IBasePropertyType
{
    public T PropertyName { get; set; }
}

... then you can implement this interface using various property types:

public class ClassName : IClassType<IChildPropertyType>
{
    public IChildPropertyType PropertyName { get; set; }
}

Another option would be to leave your interface non-generic, but to have a generic base type that explicitly implements the interface:

public class ClassBase<T> : IClassType
    where T : IChildPropertyType
{
    IBasePropertyType IClassType.PropertyName { 
        get {return PropertyName;}
        set {PropertyName = (IChildPropertyType)value;}
    }
    T PropertyName {get;set;}
}

Note that this last option is not quite ideal because you must dynamically cast the property to the given child type: while you can guarantee that every IChildProperty type is an IBasePropertyType, you cannot guarantee that every IBasePropertyType is an IChildPropertyType. However, if you can eliminate the setter from the original interface, or if you can take other steps to guarantee that the setter will never be called with the wrong type in your code, then this could work.

Update

This doesn't fundamentally change the answer much, but C# now supports covariant and contravariant interfaces. For example, if you only expect your interface to expose the getter on your property, you could mark the type as an out type:

public interface IClassType<out T>
{
    public T PropertyName { get; }
}

Implementing types don't have to change anything--they can still implement the setter if they want to:

public class ClassName : IClassType<IChildPropertyType>
{
    public IChildPropertyType PropertyName { get; set; }
}

Then, even though your class implements IClassType<IChildPropertyType>, it can still be cast as an IClassType<IBasePropertyType> and used as an argument to any method written against that interface:

    IClassType<IBasePropertyType> c = new ClassName();
    IBasePropertyType prop = c.PropertyName;
三人与歌 2024-10-22 18:17:15

你是对的,这与协方差有关;具体来说,它与虚拟方法返回类型协方差有关,这不是 C# 语言支持的一种协方差。


更新:这个答案已经有十多年了。 C# 可能很快就会实现返回类型协方差。请参阅 https://github.com/dotnet/csharplang/issues/49 了解细节。


请注意,即使确实如此,您描述的系统也不是类型安全的。假设我们有:

interface IAnimal {}
interface IGiraffe : IAnimal {}
interface ITiger: IAnimal {}
class Tiger : ITiger {}
interface IHaveAnAnimal { IAnimal Animal { get; set; } }
class C : IHaveAnAnimal
{
    public IGiraffe Animal { get; set; }
}
...
IHaveAnAnimal x = new C();
x.Animal = new Tiger(); // Uh oh. We just put a Tiger into a property of type IGiraffe.

即使协方差完全合法,这种协方差也不会合法;您必须没有设置器才能使协方差合法。

假设你没有 setter:

interface IAnimal {}
interface IGiraffe : IAnimal {}
interface ITiger: IAnimal {}
class Tiger : ITiger {}
interface IHaveAnAnimal { IAnimal Animal { get; } }
class C : IHaveAnAnimal
{
    public IGiraffe Animal { get; }
}

不幸的是这仍然不合法。但你可以这样做:

class C : IHaveAnAnimal
{
    IAnimal IHaveAnAnimal.Animal { get { return this.Animal; } }
    public IGiraffe Animal { get; }
}

现在,当 C 用作 C 时,Animal 返回长颈鹿,当用作 IHaveAnAnimal 时,它返回 IAnimal。

You are correct that this has to do with covariance; specifically it has to do with virtual method return type covariance, which is not a kind of covariance that the C# language supports.


UPDATE: This answer is over ten years old. C# may soon implement return type covariance. Please see https://github.com/dotnet/csharplang/issues/49 for details.


Note that even if it did, the system you describe is not type safe. Suppose we have:

interface IAnimal {}
interface IGiraffe : IAnimal {}
interface ITiger: IAnimal {}
class Tiger : ITiger {}
interface IHaveAnAnimal { IAnimal Animal { get; set; } }
class C : IHaveAnAnimal
{
    public IGiraffe Animal { get; set; }
}
...
IHaveAnAnimal x = new C();
x.Animal = new Tiger(); // Uh oh. We just put a Tiger into a property of type IGiraffe.

Even if the covariance were legal at all, this kind of covariance would not be legal; you'd have to have no setter for the covariance to be legal.

Suppose then you did have no setter:

interface IAnimal {}
interface IGiraffe : IAnimal {}
interface ITiger: IAnimal {}
class Tiger : ITiger {}
interface IHaveAnAnimal { IAnimal Animal { get; } }
class C : IHaveAnAnimal
{
    public IGiraffe Animal { get; }
}

Unfortunately this is still not legal. But you can do this:

class C : IHaveAnAnimal
{
    IAnimal IHaveAnAnimal.Animal { get { return this.Animal; } }
    public IGiraffe Animal { get; }
}

Now when C is used as a C, Animal returns a giraffe, and when used an an IHaveAnAnimal, it returns an IAnimal.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文