22

我是 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());
    }
}

编辑 我认为我的意图在原帖中不清楚。我的最终目标是制作一个使用不同类型的 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);
    }
}

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

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

4

8 回答 8

20

您想要的是调用 site covariance,这不是 C# 支持的功能。C# 4 及更高版本支持通用方差,但不支持调用站点方差。

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

但是,您的IAnimalGroomer<T>界面可能是逆变的:动物美容师可以在需要狗美容师的环境中使用,因为动物美容师可以美容狗。如果您通过添加到声明来实现IAnimalGroomer<T>逆变,那么您可以将 an放入.inTIAnimalGroomer<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

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

于 2013-10-09T22:44:42.030 回答
5

有两个界面,IEnumerable它们IEnumerable<T>与您要完成的工作很接近。所以你可以有一个字典Dictionary<string,IEnumerable>,它可以包含值IEnumerable<int>IEnumerable<string>等等。这里的技巧是IAnimalGroomer<T>IAnimalGroomer非通用接口派生。

编辑:

例如,根据您的要求,在创建一个调用的接口后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>();

您的代码应该可以编译和工作。

于 2013-10-09T22:03:10.863 回答
4

我知道这已经被 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>是协变的),您实际上也可以将 a 添加DogGroomer到您的列表中,尽管它List<T>不是协变的!这就是为什么我说这个名单是一个红鲱鱼。

泛型接口协变不是默认的原因是因为类型安全。我Cat/CatGroomer在您的代码中添加了与狗基本相同的类。查看 main 函数和其中的注释。

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.

于 2013-10-09T23:57:06.003 回答
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());
    }
}

现在你可以这样做:

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

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

于 2013-10-10T17:09:44.670 回答
2

这是一些有效的代码。我添加了一些类并将 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);
  }
}

如果有任何问题请告诉我

于 2013-10-09T22:02:30.317 回答
2

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

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());
    }
}

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

于 2013-10-09T21:51:16.930 回答
1

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

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();
}
于 2013-10-16T15:24:20.297 回答
1

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

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

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>在键“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);
    }
}

这仍然需要未经检查的演员表(即使您更改ObjectIAnimalGroomer<?>)。关键是您对封装的信任足以进行未经检查的强制转换。

它并没有真正增加任何东西,IAnimalGroomer<?>而不是Object在类型安全方面。因为你的封装已经确保了更多。


可以为了可读性而完成,通过IAnimalGroomer<T>实现一个存根接口来指示映射包含哪些类型的对象:

public interface IAnimalGroomerSuper { 
    // A stub interface
}

public interface IAnimalGroomer<T> : IAnimalGroomerSuper where T : IAnimal {...}

那么字典可能是:

public Dictionary<string, IAnimalGroomerSuper> animalGroomers = ...;
于 2016-05-05T09:30:03.407 回答