如何将类型不变的setter添加到协变接口?

Tam*_*inn 8 c# covariance

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

public interface IShelter<out AnimalType>
{
    AnimalType Contents { get; }
}
Run Code Online (Sandbox Code Playgroud)

然而,有一个地方,IShelter(编译时类型)被分配了一个新的动物,我们确定所设置的动物将成为一只猫.起初,我以为我可以在Contents属性中添加一个集合并执行:

IShelter<Cat> shelter = new Shelter(new Cat());
shelter.Contents = new Cat();
Run Code Online (Sandbox Code Playgroud)

但是添加setter是不可能的;

Error   CS1961  Invalid variance: The type parameter 'AnimalType' must be invariantly valid on 'IShelter<AnimalType>.Contents'. 'AnimalType' is covariant.
Run Code Online (Sandbox Code Playgroud)

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

private static void UseShelter(IShelter<Animal> s)
{
    s.Contents = new Lion();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

参数必须是object类型,以便可以在接口中指定此函数.有没有办法在编译时强制执行此操作?

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

public override IShelter<Animal> GetAnimalShelter(){
    IShelter<Cat> = new Shelter<Cat>(new Cat());
}
Run Code Online (Sandbox Code Playgroud)

一个最小的工作示例包括上面的大部分代码如下:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

Joh*_* Wu 2

在这种情况下,只需从接口中删除 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
    }
}
Run Code Online (Sandbox Code Playgroud)

DotNetFiddle 上的示例

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