C#泛型:通配符

Kyl*_*yle 21 c# generics

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

public Interface IAnimal{
  string getType();
}

public Interface IAnimalGroomer<T> where T:IAnimal{
  void groom(T);
}
Run Code Online (Sandbox Code Playgroud)

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

HashMap<String,IAnimalGroomer<?>> groomers = new HashMap<>();
Run Code Online (Sandbox Code Playgroud)

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Eri*_*ert 20

你想要的是调用站点协方差,这不是C#支持的功能.C#4及以上版本支持通用差异,但不支持呼叫站点差异.

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

然而,您的IAnimalGroomer<T>界面可能是逆变的:动物美容师可以在需要狗美容师的环境中使用,因为动物美容师可以训练狗.如果你IAnimalGroomer<T>通过添加in声明来制作逆变,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

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

  • @Kyle:如果你想在运行时弄清楚你想要使用哪一个,那么也许你不应该考虑*generics*开始; 你的代码似乎需要*特定的*类型,而不是*generic*.基于类型的运行时决策与*generics*不能很好地混合; 如果你问我,它就会失败. (4认同)
  • *"但是有一个很好的理由.它迫使IAnimalGroomer的实施与特定类型的动物联系起来."*.为什么通过*generics*?只需使用`void Groom(IAnimal animal);`实现一个非通用的`IAnimalGroom`接口,你就可以将它绑在一起.我认为你的场景太"类型耦合"了*genercis*是有用的,至少在C#中,类型系统正在对你起作用而不是帮助.恕我直言你最好使用非通用接口. (2认同)

Bol*_*glu 5

有两个接口,IEnumerable并且IEnumerable<T>它们接近你试图完成什么.因此,可以有一本字典一样,你Dictionary<string,IEnumerable>可以包含任意值IEnumerable<int>,IEnumerable<string>等这里的窍门是派生IAnimalGroomer<T>IAnimalGroomer,非通用接口.

编辑:

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

public interface IAnimalGroomer{
}
Run Code Online (Sandbox Code Playgroud)

,如果您更改以下行:

public interface IAnimalGroomer<T> where T:IAnimal{
Run Code Online (Sandbox Code Playgroud)

public interface IAnimalGroomer<T> : IAnimalGroomer where T:IAnimal{
Run Code Online (Sandbox Code Playgroud)

以及读取的行:

private List<IAnimalGroomer<IAnimal>> groomers = new List<IAnimalGroomer<IAnimal>>();
Run Code Online (Sandbox Code Playgroud)

private List<IAnimalGroomer> groomers=new List<IAnimalGroomer>();
Run Code Online (Sandbox Code Playgroud)

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


InB*_*een 3

正如布莱恩在上面的评论中指出的那样,也许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());
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以这样做:

var animalClinic = new AnimalClinic();
animalClinic.EmployGroomer(new DogGroomer());
animalClinic.EmployGroomer(new CatGroomer());
animalClinic.Groom(new Dog());
animalClinic.Groom(new Cat());
animalClinic.Groom(new BigBadWolf());
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是您所寻找的。希望能帮助到你!

  • 我不同意这种做法。使用动态来方便地忽略类型系统并不是一个好的编程实践。有几个答案定义了一个非通用接口,可以满足要求,而无需诉诸动态。就我个人而言,我将上述内容列为反模式。 (3认同)
  • 我会因“窃取”Eric Lippert 的录取通知书而获得徽章吗?:p (2认同)