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
是逆变的 。 但是狗的比较可能不会被用来比较动物; 有人可以试着比较两只猫。
如果仍然不清楚,请先阅读常见问题解答:
https://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx
然后回来问你有更多的问题。
有两个接口, IEnumerable
和IEnumerable
,它们与你想要完成的接近。 所以你可以有一个字典,如Dictionary
,它可以包含IEnumerable
, IEnumerable
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)
方法,它不能被明确地变成协变 。 在一般情况下将IA
为IA
是非法的,或者换句话说,默认情况下,通用接口不是协变的。 List
方法触发从DogGroomer
( IAnimalGroomer
)到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 = ...;
关键是在幕后使用非通用接口来限制类型,但只暴露通用版本。
上述就是C#学习教程:C#generics:通配符分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
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(); }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1029535.html