C# 泛型:通配符
本文关键字:通配符 泛型 | 更新日期: 2023-09-27 18:34:44
我是 c# 世界的新手,我正在尝试将我的头缠绕在泛型上。这是我目前的问题:
public Interface IAnimal{
string getType();
}
public Interface IAnimalGroomer<T> 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<Dog>
{
public void Groom(Dog dog)
{
dog.ClipNails();
}
}
public class Program
{
private List<IAnimalGroomer<IAnimal>> groomers = new List<IAnimalGroomer<IAnimal>>();
public void doSomething()
{
//THIS DOESN"T COMPILE!!!!
groomers.Add(new DogGroomer());
}
}
编辑我认为我的意图在原始帖子中不清楚。我的最终目标是建立一个动物美容诊所,雇用不同类型的IAnimalGroomer。然后动物主人可以将动物送到诊所,诊所可以决定哪个美容师应该照顾动物:
public class AnimalGroomerClinic
{
public Dictionary<String, IAnimalGroomer> animalGroomers = new Dictionary<String,IAnimalGroomer>();
public void employGroomer(IAnimalGroomer groomer){
animalGroomers.add(groomer.getAnimalType(), groomer);
}
public void Groom(IAnimal animal){
animalGroomers[animal.getAnimalType()].Groom(animal);
}
}
我意识到我可以在不使用泛型的情况下做到这一点。但是泛型允许我以这样一种方式编写IAnimalGroomer
接口,即它(在编译时(绑定到IAnimal
的特定实例。此外,具体的IAnimalGroomer
类不需要一直转换它们的IAnimals
,因为泛型会强制实现处理一种特定的动物。 我以前在 Java 中使用过这个成语,我只是想知道是否有类似的方法来用 C# 编写它。
编辑 2:很多有趣的讨论。我接受一个在评论中指出我动态调度的答案。
你想要的是调用站点协方差,这不是 C# 支持的功能。C# 4 及更高版本支持泛型方差,但不支持调用站点方差。
但是,这在这里对您没有帮助。您希望将狗美容师放入动物美容师列表中,但这在 C# 中不起作用。狗美容师不能在任何需要动物美容师的情况下使用,因为狗美容师只能梳理狗,但动物美容师也可以梳理猫。 也就是说,当接口无法以协变方式安全地使用时,您希望接口是协变的。
但是,您的IAnimalGroomer<T>
界面可能是逆变的:动物美容师可以在需要狗美容师的情况下使用,因为动物美容师可以梳理狗。如果您通过在T
声明中添加in
来IAnimalGroomer<T>
逆变,那么您可以将IAnimalGroomer<IAnimal>
放入IList<IAnimalGroomer<Dog>>
中。
对于更现实的例子,请考虑 IEnumerable<T>
vs IComparer<T>
。狗的序列可以用作动物的序列; IEnumerable<T>
是协变的。 但是动物序列不能用作狗序列;那里可能有一只老虎。
相比之下,比较动物的比较器可以用作狗的比较器; IComparer<T>
是逆变的。但不能使用狗的比较器来比较动物;有人可以尝试比较两只猫。
如果这仍然不清楚,请首先阅读常见问题解答:
http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx
然后回来问更多问题,如果你有的话。
有两个接口,IEnumerable
和 IEnumerable<T>
接近您要完成的任务。所以你可以有一个像Dictionary<string,IEnumerable>
这样的字典,它可以包含IEnumerable<int>
、IEnumerable<string>
等值。这里的诀窍是从IAnimalGroomer
(一个非泛型接口(派生IAnimalGroomer<T>
。
编辑:
例如,根据您的请求,在创建名为 IAnimalGroomer
的接口后,使用:
public interface IAnimalGroomer{
}
,如果更改以下行:
public interface IAnimalGroomer<T> where T:IAnimal{
自
public interface IAnimalGroomer<T> : IAnimalGroomer where T:IAnimal{
以及以下行:
private List<IAnimalGroomer<IAnimal>> groomers = new List<IAnimalGroomer<IAnimal>>();
自
private List<IAnimalGroomer> groomers=new List<IAnimalGroomer>();
您的代码应该可以编译并工作。
这已经被Lipperted了,但我仍然想回答。列表在这里是一个红鲱鱼,你使用它并不重要。
这不起作用的原因是IAnimalGroomer<T>
本身不是协变的,并且由于groom(T)
方法,它不能显式地成为协变。在一般情况下,将IA<Derived>
转换为IA<Base>
是非法的,或者换句话说,默认情况下泛型接口不是协变的。List<T>.Add
方法是触发从 DogGroomer
(IAnimalGroomer<Dog>
(到 IAnimalGroomer<IAnimal>
的转换,但例如,这仍然不起作用:
IAnimalGroomer<Dog> doggroomer = new DogGroomer(); // fine
IAnimalGroomer<IAnimal> animalgroomer = doggroomer; // invalid cast, you can explicitly cast it
// in which case it fails at run time
如果这有效(所以如果IAnimalGroomer<T>
是协变的(,您实际上也可以向列表中添加一个DogGroomer
,尽管List<T>
不是协变的!这就是为什么我说这个名单是一条红鲱鱼。
泛型接口协方差不是默认值的原因是类型安全。我在您的代码中添加了Cat/CatGroomer
类,这些类与狗的代码基本相同。查看主函数和其中的注释。
public interface IAnimal
{
string getType();
}
public interface IAnimalGroomer<T> where T:IAnimal
{
void groom(T t);
}
public class Dog : IAnimal
{
public string getType() { return "DOG"; }
public void clipNails() { }
}
public class DogGroomer : IAnimalGroomer<Dog>
{
public void groom(Dog dog)
{
dog.clipNails();
}
}
public class Cat : IAnimal
{
public string getType() { return "CAT"; }
public void clipNails() { }
}
public class CatGroomer : IAnimalGroomer<Cat>
{
public void groom(Cat cat)
{
cat.clipNails();
}
}
public class Program
{
static void Main(string[] args)
{
// this is fine.
IAnimalGroomer<Dog> doggroomer = new DogGroomer();
// this is an invalid cast, but let's imagine we allow it!
IAnimalGroomer<IAnimal> 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<Derived>
转换为 A<Base>
。但是,您不能再使用以 T 作为参数的方法,而您这样做了。但它消除了试图将猫推入狗的问题。
IEnumerable<T>
是协变接口的一个例子 - 它没有f(T)
方法,因此问题不会发生,不像您的groom(T) method
。
正如布莱恩在上面的评论中指出的那样,也许dynamic
是要走的路。
查看以下代码。您可以获得泛型的好处,可以很好地绑定 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<T> where T:IAnimal
{
public Type AnimalType { get { return typeof(T); } }
public abstract void Groom(T animal);
}
public class CatGroomer : AnimalGroomer<Cat>
{
public override void Groom(Cat animal)
{
Console.WriteLine("{0} groomed by {1}", animal.GetType(), this.GetType());
}
}
public class DogGroomer : AnimalGroomer<Dog>
{
public override void Groom(Dog animal)
{
Console.WriteLine("{0} groomed by {1}", animal.GetType(), this.GetType());
}
}
public class AnimalClinic
{
private Dictionary<Type, dynamic> groomers = new Dictionary<Type, dynamic>();
public void EmployGroomer<T>(AnimalGroomer<T> 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());
我不确定这是否是您想要的。希望对您有所帮助!
下面是一些有效的代码。 我添加了一些类,并将 AnimalGroomer 切换为抽象类而不是接口:
class Program
{
static void Main(string[] args)
{
var dict = new Dictionary<string, IGroomer>();
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<Dog>
{
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<T> : 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<IAnimalGroomer> groomers = new List<IAnimalGroomer>();
public void doSomething()
{
groomers.Add(new DogGroomer());
}
}
或者,也许您需要另一种技术设计来解决您的问题
我反对使用dynamic
,因为它有运行时成本。
一种更简单的解决方案,使用可以安全地存储任何IAnimalGroomer<T>
Dictionary<string, object>
。
public class AnimalGroomerClinic {
public Dictionary<string, object> animalGroomers = new Dictionary<string, object>();
public void employGroomer<T>(IAnimalGroomer<T> groomer) where T : IAnimal {
animalGroomers.Add(groomer.getAnimalType(), groomer);
}
public void Groom<T>(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<T>).groom(animal);
}
}
现在,这需要一个石膏,你可能会说这是不安全的。但是您知道由于封装,它是安全的。如果您在"狗"键下的哈希图中输入IAnimalGroomer<Dog>
。并用钥匙"狗"再次请求它,你知道它仍然是一个IAnimalGroomer<Dog>
.
就像Java等价物一样:
class AnimalGroomerClinic {
public Map<String, Object> animalGroomers = new HashMap<>();
public <T extends IAnimal> void employGroomer(IAnimalGroomer<T> groomer) {
animalGroomers.put(groomer.getAnimalType(), groomer);
}
@SuppressWarnings("unchecked")
public <T extends IAnimal> void Groom(T animal) {
((IAnimalGroomer<T>) animalGroomers.get(animal.getAnimalType())).groom(animal);
}
}
这仍然需要未经检查的转换(即使您将Object
更改为IAnimalGroomer<?>
(。关键是你足够信任你的封装来执行未经检查的转换。
就类型安全性而言,它并没有真正增加任何东西来IAnimalGroomer<?>
而不是Object
。因为你的封装已经确保更多。
为了可读性,可以通过IAnimalGroomer<T>
实现存根接口来指示映射包含的对象类型:
public interface IAnimalGroomerSuper {
// A stub interface
}
public interface IAnimalGroomer<T> : IAnimalGroomerSuper where T : IAnimal {...}
那么字典可以是:
public Dictionary<string, IAnimalGroomerSuper> 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<T> : IGroomer where T : IAnimal
{
void Groom(T animal);
}
public class DogGroomer : IGroomer<Dog>
{
public void Groom(Dog dog) => Console.WriteLine(dog.Woof);
}
public class MeanDogGroomer : IGroomer<Dog>
{
public void Groom(Dog dog) => Console.WriteLine(dog.Growl);
}
public class CatGroomer : IGroomer<Cat>
{
public void Groom(Cat cat) => Console.WriteLine(cat.Purr);
}
public class AnimalClinic
{
private TypedLookup<IGroomer> _groomers = new TypedLookup<IGroomer>();
public void Add<T>(IGroomer<T> groomer) where T : IAnimal
=> _groomers.Add<T>(groomer);
public void Groom<T>(T animal) where T : IAnimal
=> _groomers.OfType<T, IGroomer<T>>().ToList().ForEach(g => g.Groom(animal));
}
public class TypedLookup<T> : Dictionary<Type, IList<T>>
{
public void Add<TType>(T item)
{
IList<T> list;
if(TryGetValue(typeof(TType), out list))
list.Add(item);
else
this[typeof(TType)] = new List<T>{item};
}
public IEnumerable<TRet> OfType<TType, TRet>() => this[typeof(TType)].Cast<TRet>();
public TRet First<TType, TRet>() => this[typeof(TType)].Cast<TRet>().First();
}