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# 支持的功能。C# 4 及更高版本支持泛型方差,但不支持调用站点方差。

但是,这在这里对您没有帮助。您希望将狗美容师放入动物美容师列表中,但这在 C# 中不起作用。狗美容师不能在任何需要动物美容师的情况下使用,因为狗美容师只能梳理狗,但动物美容师也可以梳理猫。 也就是说,当接口无法以协变方式安全地使用时,您希望接口是协变的。

但是,您的IAnimalGroomer<T>界面可能是逆变的:动物美容师可以在需要狗美容师的情况下使用,因为动物美容师可以梳理狗。如果您通过在T声明中添加inIAnimalGroomer<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

然后回来问更多问题,如果你有的话。

有两个接口,IEnumerableIEnumerable<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();
}