我有一个类型 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
}
}
System.Collections.Generic
下的许多 CLR 类遵循相同的模式.许多类实现 IEnumerable<T>
,这是协变的;但是如果你想调用允许你添加的方法,你必须将它作为一个具体的类来引用,比如 List<T>
.或者,如果您真的想通过界面添加,您可以使用 IList<T>
.但在任何情况下都没有一个单一的协变接口(interface)也允许您添加。
关于c# - 如何将类型不变的 setter 添加到协变接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53204536/