C#generics:通配符

我是c#世界的新手,我正试图围绕仿制药。 这是我目前的问题:

public Interface IAnimal{ string getType(); } public Interface IAnimalGroomer where T:IAnimal{ void groom(T); } 

现在我想要一本包含这些动物美容师的字典。 我怎么做? 在java中,我可以这样做:

 HashMap<String,IAnimalGroomer> groomers = new HashMap(); 

编辑:这是我正在尝试做的一个例子:

 public class Dog : IAnimal { public string GetType() { return "DOG"; } public void ClipNails() { } } public class DogGroomer : IAnimalGroomer { public void Groom(Dog dog) { dog.ClipNails(); } } public class Program { private List<IAnimalGroomer> groomers = new List<IAnimalGroomer>(); public void doSomething() { //THIS DOESN"T COMPILE!!!! groomers.Add(new DogGroomer()); } } 

编辑我认为我的意图在原帖中不明确。 我的最终目标是制作一个使用不同类型的IAnimalGroomers的AnimalGroomerClinic。 然后动物主人可以在诊所放下动物,诊所可以决定哪个美容师应该照顾动物:

 public class AnimalGroomerClinic { public Dictionary animalGroomers = new Dictionary(); public void employGroomer(IAnimalGroomer groomer){ animalGroomers.add(groomer.getAnimalType(), groomer); } public void Groom(IAnimal animal){ animalGroomers[animal.getAnimalType()].Groom(animal); } } 

我意识到我可以不使用generics来做到这一点。 但是generics允许我以这样的方式编写IAnimalGroomer接口,即它(在编译时)绑定到IAnimal的特定实例。 此外, IAnimalGroomer具体类不需要一直投射它们的IAnimals ,因为generics会迫使实现处理一种特定类型的动物。 我以前在Java中使用过这个习惯用法,我只是想知道是否有类似的方法在C#中编写它。

编辑2:很多有趣的讨论。 我接受了一个答案,指出我在评论中动态调度。

正如布莱恩在上面的评论中指出的那样,也许dynamic是走到这里的方式。

查看以下代码。 您可以获得generics的好处,以便很好地控制API,并使用dynamic来使事情有效

 public interface IAnimal { } public class Dog : IAnimal { } public class Cat : IAnimal { } public class BigBadWolf : IAnimal { } //I changed `IAnimalGroomer` to an abstract class so you don't have to implement the `AnimalType` property all the time. public abstract class AnimalGroomer where T:IAnimal { public Type AnimalType { get { return typeof(T); } } public abstract void Groom(T animal); } public class CatGroomer : AnimalGroomer { public override void Groom(Cat animal) { Console.WriteLine("{0} groomed by {1}", animal.GetType(), this.GetType()); } } public class DogGroomer : AnimalGroomer { public override void Groom(Dog animal) { Console.WriteLine("{0} groomed by {1}", animal.GetType(), this.GetType()); } } public class AnimalClinic { private Dictionary groomers = new Dictionary(); public void EmployGroomer(AnimalGroomer groomer) where T:IAnimal { groomers.Add(groomer.AnimalType, groomer); } public void Groom(IAnimal animal) { dynamic groomer; groomers.TryGetValue(animal.GetType(), out groomer); if (groomer != null) groomer.Groom((dynamic)animal); else Console.WriteLine("Sorry, no groomer available for your {0}", animal.GetType()); } } 

现在你可以这样做:

 var animalClinic = new AnimalClinic(); animalClinic.EmployGroomer(new DogGroomer()); animalClinic.EmployGroomer(new CatGroomer()); animalClinic.Groom(new Dog()); animalClinic.Groom(new Cat()); animalClinic.Groom(new BigBadWolf()); 

我不确定这是不是你想要的。 希望能帮助到你!

你想要的是调用站点协方差 ,这不是C#支持的function。 C#4及更高版本支持通用差异,但不支持呼叫站点差异。

但是,这对你没有帮助。 你想要一个狗美容师被列入动物美容师列表,但这在C#中无效。 狗美容师不能用于需要动物美容师的任何环境中,因为狗美容师只能养狗,但动物美容师也可以训练猫。 也就是说,当无法以协变方式安全使用时,您希望接口是变的。

然而,您的IAnimalGroomer界面可能是逆变的 :动物美容师可以在需要狗美容师的环境中使用,因为动物美容师可以训练狗。 如果你通过添加到T的声明来使IAnimalGroomer逆变,那么你可以将IAnimalGroomer放入IList>

有关更实际的示例,请考虑IEnumerable vs IComparer 。 一系列狗可以用作动物序列; IEnumerable协变的 。 但是一系列动物可能不会被用作一系列的狗; 那里可能有一只老虎。

相比之下,比较动物的比较者可以用作狗的比较者; IComparer逆变的 。 但是狗的比较可能不会被用来比较动物; 有人可以试着比较两只猫。

如果仍然不清楚,请先阅读常见问题解答:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

然后回来问你有更多的问题。

有两个接口, IEnumerableIEnumerable ,它们与你想要完成的接近。 所以你可以有一个字典,如Dictionary ,它可以包含IEnumerableIEnumerable IAnimalGroomer 。这里的技巧是从IAnimalGroomer派生IAnimalGroomer ,这是一个非通用的接口。

编辑:

例如,根据您的请求,在创建名为IAnimalGroomer的接口后:

 public interface IAnimalGroomer{ } 

,如果您更改以下行:

 public interface IAnimalGroomer where T:IAnimal{ 

 public interface IAnimalGroomer : IAnimalGroomer where T:IAnimal{ 

和读取的行:

 private List> groomers = new List>(); 

 private List groomers=new List(); 

你的代码应该编译和工作。

我知道这是Lipperted,但我还是想回答。 列表在这里是一个红鲱鱼,你使用它并不重要。

这不起作用的原因是因为IAnimalGroomer本身不是协变的,并且由于IAnimalGroomer groom(T)方法,它不能被明确地变成协变 。 在一般情况下将IAIA是非法的,或者换句话说,默认情况下,通用接口不是协变的。 List.Add方法触发从DogGroomerIAnimalGroomer )到IAnimalGroomer ,但是例如,这仍然不起作用:

 IAnimalGroomer doggroomer = new DogGroomer(); // fine IAnimalGroomer animalgroomer = doggroomer; // invalid cast, you can explicitly cast it // in which case it fails at run time 

如果这有效(所以如果IAnimalGroomer是协变的),你实际上也可以在你的列表中添加DogGroomer ,尽管List不是协变的! 这就是我说清单是红鲱鱼的原因。

通用接口协方差不是默认值的原因是因为类型安全。 我在您的代码中添加了Cat/CatGroomer类, Cat/CatGroomer类与狗的代码基本相同。 查看主要function及其中的注释。

 public interface IAnimal { string getType(); } public interface IAnimalGroomer where T:IAnimal { void groom(T t); } public class Dog : IAnimal { public string getType() { return "DOG"; } public void clipNails() { } } public class DogGroomer : IAnimalGroomer { public void groom(Dog dog) { dog.clipNails(); } } public class Cat : IAnimal { public string getType() { return "CAT"; } public void clipNails() { } } public class CatGroomer : IAnimalGroomer { public void groom(Cat cat) { cat.clipNails(); } } public class Program { static void Main(string[] args) { // this is fine. IAnimalGroomer doggroomer = new DogGroomer(); // this is an invalid cast, but let's imagine we allow it! IAnimalGroomer animalgroomer = doggroomer; // compile time, groom parameter must be IAnimal, so the following is legal, as Cat is IAnimal // but at run time, the groom method the object has is groom(Dog dog) and we're passing a cat! we lost compile-time type-safety. animalgroomer.groom(new Cat()); } } 

没有使用序列,但如果合法,代码仍会破坏类型安全性。

可以允许这种类型的强制转换,但由它引起的错误将在运行时发生,我认为这是不可取的。

如果将类型参数T标记为“out”,则可以将A转换为A 。 但是,您不能再使用T作为参数的方法。 但它消除了试图将猫推入狗的问题。

IEnumerable是协变接口的一个例子 – 它没有 f(T)方法,所以问题不会发生,这与你的groom(T) method

这是一些有效的代码。 我添加了一些类并将AnimalGroomer切换为抽象类而不是接口:

 class Program { static void Main(string[] args) { var dict = new Dictionary(); dict.Add("Dog", new DogGroomer()); // use it IAnimal fido = new Dog(); IGroomer sample = dict["Dog"]; sample.Groom(fido); Console.WriteLine("Done"); Console.ReadLine(); } } // actual implementation public class Dog : IAnimal { } public class DogGroomer : AnimalGroomer { public override void Groom(Dog beast) { Console.WriteLine("Shave the beast"); } } public interface IAnimal { } public interface IGroomer { void Groom(object it); } public abstract class AnimalGroomer : IGroomer where T : class, IAnimal { public abstract void Groom(T beast); public void Groom(object it) { if (it is T) { this.Groom(it as T); return; } throw new ArgumentException("The argument is not a " + typeof(T).GetType().Name); } } 

如果有任何问题,请告诉我

根据我的理解,在这种情况下,您不能将类型约束放在参数中。 这意味着您可能需要进行装箱和拆箱。 您可能需要使用普通接口。

 public interface IAnimal{ string GetType(); } public interface IAnimalGroomer{ void Groom(IAnimal dog); } public class Dog : IAnimal { public string GetType() { return "DOG"; } public void ClipNails() { } } public class DogGroomer : IAnimalGroomer { public void Groom(IAnimal dog) { if (dog is Dog) { (dog as Dog).ClipNails(); } else { // something you want handle. } } } public class Program { private List groomers = new List(); public void doSomething() { groomers.Add(new DogGroomer()); } } 

或者您可能需要另外一个技术设计来解决您的问题

我不习惯使用dynamic ,因为它有运行时成本。

一个更简单的解决方案是使用Dictionary ,您可以在其中安全地存储任何IAnimalGroomer

 public class AnimalGroomerClinic { public Dictionary animalGroomers = new Dictionary(); public void employGroomer(IAnimalGroomer groomer) where T : IAnimal { animalGroomers.Add(groomer.getAnimalType(), groomer); } public void Groom(T animal) where T : IAnimal { // Could also check here if the 'as' operator returned null, // which might happen if you don't have the specific groomer (animalGroomers[animal.getAnimalType()] as IAnimalGroomer).groom(animal); } } 

现在,这需要一个演员,你可能会说这是不安全的。 但是你知道由于封装它是安全的。 如果将IAnimalGroomer放入键“dog”下的hashmap中。 并使用键“dog”再次请求它,你知道它仍然是一个IAnimalGroomer

就像java等价:

 class AnimalGroomerClinic { public Map animalGroomers = new HashMap<>(); public  void employGroomer(IAnimalGroomer groomer) { animalGroomers.put(groomer.getAnimalType(), groomer); } @SuppressWarnings("unchecked") public  void Groom(T animal) { ((IAnimalGroomer) animalGroomers.get(animal.getAnimalType())).groom(animal); } } 

仍然需要未经检查的强制转换(即使您将Object更改为IAnimalGroomer )。 关键是你相信你的封装足以做一个未经检查的演员。

在类型安全方面,它并没有真正添加IAnimalGroomer而不是Object 。 因为你的封装已经确保了更多。


为了便于阅读,可以通过让IAnimalGroomer实现存根接口来指示地图所拥有的对象类型:

 public interface IAnimalGroomerSuper { // A stub interface } public interface IAnimalGroomer : IAnimalGroomerSuper where T : IAnimal {...} 

然后字典可以是:

 public Dictionary animalGroomers = ...; 

关键是在幕后使用非通用接口来限制类型,但只暴露通用版本。

 void Main() { var clinic = new AnimalClinic(); clinic.Add(new CatGroomer()); clinic.Add(new DogGroomer()); clinic.Add(new MeanDogGroomer()); clinic.Groom(new Cat()); //Purr clinic.Groom(new Dog()); //Woof , Grrr! } public interface IAnimal {} public interface IGroomer {} public class Dog : IAnimal { public string Woof => "Woof"; public string Growl => "Grrr!"; } public class Cat : IAnimal { public string Purr => "Purr"; } public interface IGroomer : IGroomer where T : IAnimal { void Groom(T animal); } public class DogGroomer : IGroomer { public void Groom(Dog dog) => Console.WriteLine(dog.Woof); } public class MeanDogGroomer : IGroomer { public void Groom(Dog dog) => Console.WriteLine(dog.Growl); } public class CatGroomer : IGroomer { public void Groom(Cat cat) => Console.WriteLine(cat.Purr); } public class AnimalClinic { private TypedLookup _groomers = new TypedLookup(); public void Add(IGroomer groomer) where T : IAnimal => _groomers.Add(groomer); public void Groom(T animal) where T : IAnimal => _groomers.OfType>().ToList().ForEach(g => g.Groom(animal)); } public class TypedLookup : Dictionary> { public void Add(T item) { IList list; if(TryGetValue(typeof(TType), out list)) list.Add(item); else this[typeof(TType)] = new List{item}; } public IEnumerable OfType() => this[typeof(TType)].Cast(); public TRet First() => this[typeof(TType)].Cast().First(); }