c# - 如何将类型不变的 setter 添加到协变接口(interface)?

标签 c# covariance

我有一个类型 Shelter 需要协变,以便另一个类*中的覆盖可以返回 Shelter<Cat>其中一个 Shelter<Animal>是期待。由于类在 C# 中不能协变或逆变,因此我添加了一个接口(interface):

public interface IShelter<out AnimalType>
{
    AnimalType Contents { get; }
}

但是,有一个地方为 IShelter(编译时类型)分配了一个新动物,我们可以肯定地知道被设置的动物将是一只猫。起初,我以为我可以只向 Contents 属性添加一个集合并执行以下操作:

IShelter<Cat> shelter = new Shelter(new Cat());
shelter.Contents = new Cat();

但是添加setter是不可能的;

Error   CS1961  Invalid variance: The type parameter 'AnimalType' must be invariantly valid on 'IShelter<AnimalType>.Contents'. 'AnimalType' is covariant.

这是有道理的,否则我可以将猫舍传递给这个函数:

private static void UseShelter(IShelter<Animal> s)
{
    s.Contents = new Lion();
}

但是,我不会那样做。如果有某种方法可以将 setter 标记为不变的,这样 UseShelter 函数就只能分配一个 Animal,这样就可以在编译时强制执行。我需要这个的原因是因为我的代码中有一个地方知道它有一个 Shelter<Cat> ,并且需要将 Contents 属性重新分配给新的 Cat。

到目前为止,我找到的解决方法是在显式设置函数中添加一个 jucky 运行时类型检查; SCSS !

public void SetContents(object newContents)
{
    if (newContents.GetType() != typeof(AnimalType))
    {
        throw new InvalidOperationException("SetContents must be given the correct AnimalType");
    }
    Contents = (AnimalType)newContents;
}

参数需要是object类型,这样才能在接口(interface)中指定这个函数。有什么办法可以在编译时强制执行此操作吗?

* 澄清一下,有一个函数:public virtual IShelter<Animal> GetAnimalShelter()被覆盖并返回 IShelter<Cat> :

public override IShelter<Animal> GetAnimalShelter(){
    return new Shelter<Cat>(new Cat());
}

包含上述大部分代码的最小工作示例如下:

class Animal { }
class Cat : Animal { }
class Lion : Animal { }

public interface IShelter<out AnimalType>
{
    AnimalType Contents { get; }

    void SetContents(object newContents);
}

class Shelter<AnimalType> : IShelter<AnimalType>
{
    public Shelter(AnimalType animal)
    {
    }

    public void SetContents(object newContents)
    {
        if (newContents.GetType() != typeof(AnimalType))
        {
            throw new InvalidOperationException("SetContents must be given the correct AnimalType");
        }
        Contents = (AnimalType)newContents;
    }

    public AnimalType Contents { get; set; }
}

class Usage
{
    public static void Main()
    {
        IShelter<Cat> catshelter = new Shelter<Cat>(new Cat());
        catshelter.SetContents(new Cat());
        catshelter.SetContents(new Lion()); // should be disallowed by the compiler
    }
}

最佳答案

在这种情况下,只需从接口(interface)中删除 setter 并使用具体类型(或使用 var 推断它,如本例所示)。毕竟,如果代码“知道”肯定要添加一只猫,它可能也知道避难所的具体类型。

interface IShelter<out AnimalType>
{
    AnimalType Contents { get; }
}

class Shelter<AnimalType> : IShelter<AnimalType>
{
    public Shelter(AnimalType animal)
    {
    }

    public void SetContents(AnimalType newContents)
    {
        Contents = newContents;
    }

    public AnimalType Contents { get; set; }
}

public class Usage
{
    public static void Main()
    {
        var catshelter = new Shelter<Cat>(new Cat());
        catshelter.SetContents(new Cat());
        catshelter.SetContents(new Lion()); // Is disallowed by the compiler
    }
}

Example on DotNetFiddle

System.Collections.Generic 下的许多 CLR 类遵循相同的模式.许多类实现 IEnumerable<T> ,这是协变的;但是如果你想调用允许你添加的方法,你必须将它作为一个具体的类来引用,比如 List<T> .或者,如果您真的想通过界面添加,您可以使用 IList<T> .但在任何情况下都没有一个单一的协变接口(interface)也允许您添加。

关于c# - 如何将类型不变的 setter 添加到协变接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53204536/

相关文章:

c# - AES加密错误: The input data is not a complete block?

C# WCF 的行为类似于单个用户实例

c# - asp.net core (mvc) ViewModel请求主键

python - numpy.polyfit 没有关键字 'cov'

java - 基元的方法返回值协方差。有效吗?

c# - 有什么简单的方法可以连接两个 BitArray (C# .NET)?

c# - foreach 循环无法将类型转换为它实现的接口(interface)

typescript - Typescript中协变和逆变位置的区别

covariance - 有人可以解释类型协方差/逆变和范畴论之间的联系吗?

C# DLL 菜鸟,如何获取函数