簡體   English   中英

C#泛型:通配符

[英]C# Generics: wildcards

我是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:很多有趣的討論。 我接受了一個答案,指出我在評論中動態調度。

你想要的是調用站點協方差 ,這不是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

然后回來問你有更多的問題。

有兩個接口, IEnumerableIEnumerable<T> ,它們與你想要完成的接近。 所以你可以有一個字典,比如Dictionary<string,IEnumerable> ,它可以包含IEnumerable<int>IEnumerable<string> IAnimalGroomer<T> 。這里的技巧是從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>本身不是協變的,並且由於IAnimalGroomer<T> groom(T)方法,它不能被明確地變成協變 在一般情況下將IA<Derived>IA<Base>是非法的,或者換句話說,通用接口默認情況下不協變。 List<T>.Add方法觸發從DogGroomerIAnimalGroomer<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類, 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

根據我的理解,在這種情況下,您不能將類型約束放在參數中。 這意味着您可能需要進行裝箱和拆箱。 您可能需要使用普通接口。

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

或者您可能需要另外一個技術設計來解決您的問題

這是一些有效的代碼。 我添加了一些類並將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);
  }
}

如果有任何問題,請告訴我

正如布萊恩在上面的評論中指出的那樣,也許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());

我不確定這是不是你想要的。 希望能幫助到你!

關鍵是在幕后使用非通用接口來限制類型,但只暴露通用版本。

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

我不習慣使用dynamic ,因為它有運行時成本。

一個更簡單的解決方案是使用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”下的hashmap中。 並使用鍵“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 = ...;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM