Подтвердить что ты не робот

С# Generics: подстановочные знаки

Я новичок в мире С#, и я пытаюсь склонить голову к дженерикам. Вот моя текущая проблема:

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

ИЗМЕНИТЬ Я думаю, что мои намерения были непонятны в первоначальном посте. Моя конечная цель - сделать AnimalGroomerClinic, который использует различные типы IAnimalGroomers. Тогда владельцы животных могут опустить животных в клинику, и клиника может решить, какой грумер должен заботиться о животном:

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, и мне просто интересно, есть ли способ написать ее на С#.

Изменить 2: Много интересного обсуждения. Я принимаю ответ, который указал мне на динамическую отправку комментариев.

4b9b3361

Ответ 1

Как Брайан указал в комментариях выше, может быть, 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());

Я не уверен, что это то, что вы искали. Надеюсь, это поможет!

Ответ 2

То, что вы хотите, это ковариантность сайта вызова, которая не является функцией, поддерживаемой С#. С# 4 и выше поддерживают общую дисперсию, но не дисперсию сайта.

Однако это вам не поможет. Вы хотите, чтобы собака-грумер была помещена в список животных-помещиков, но это не может работать на С#. Собака-грумер не может использоваться ни в каком контексте, в котором необходим живот-грумер, потому что собачий грумер может жениться только на собаках, но животное-грумер может также женихать кошек. То есть, вы хотите, чтобы интерфейс был ковариантным, если его нельзя безопасно использовать ковариантным образом.

Однако ваш интерфейс IAnimalGroomer<T> может быть контравариантным, поскольку он стоит: животный грумер может использоваться в контексте, в котором требуется собачий грумер, потому что животное-грумер может женихать собак. Если вы сделали контравариант IAnimalGroomer<T>, добавив in в объявление T, вы можете поместить IAnimalGroomer<IAnimal> в IList<IAnimalGroomer<Dog>>.

Для более реалистичного примера подумайте о IEnumerable<T> vs IComparer<T>. Последовательность собак может быть использована в качестве последовательности животных; IEnumerable<T> ковариантно. Но последовательность животных не может использоваться в качестве последовательности собак; там мог быть тигр.

В противоположность этому, сравнитель, который сравнивает животных, может использоваться в качестве компаньона собак; IComparer<T> является контравариантным. Но сравнение собак не может быть использовано для сравнения животных; кто-то может попытаться сравнить двух кошек.

Если это еще не ясно, начните с чтения FAQ:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

а затем вернитесь и задайте больше вопросов, если они у вас есть.

Ответ 3

Существует два интерфейса: IEnumerable и IEnumerable<T>, которые близки к тому, что вы пытаетесь выполнить. Таким образом, вы можете иметь словарь типа Dictionary<string,IEnumerable>, который может содержать как значения IEnumerable<int>, IEnumerable<string> и т.д. Трюк здесь заключается в выводе IAnimalGroomer<T> из IAnimalGroomer, не общего интерфейса.

EDIT:

В качестве примера, для вашего запроса, после создания интерфейса с именем IAnimalGroomer с помощью:

public interface IAnimalGroomer{
}

если вы измените строку, которая гласит:

public interface IAnimalGroomer<T> where T:IAnimal{

to

public interface IAnimalGroomer<T> : IAnimalGroomer where T:IAnimal{

и строка, которая гласит:

private List<IAnimalGroomer<IAnimal>> groomers = new List<IAnimalGroomer<IAnimal>>();

to

private List<IAnimalGroomer> groomers=new List<IAnimalGroomer>();

ваш код должен компилироваться и работать.

Ответ 4

Я знаю, что это было Lipperted, но я все еще чувствую, что отвечаю. Список - это красная селедка, неважно, что вы его используете.

Причина, по которой это не работает, , потому что IAnimalGroomer<T> сама по себе не является ковариантной, и она не может быть сделана ковариантной явно из-за метода groom(T). Заключить IA<Derived> to IA<Base> в общем случае запрещено, иначе говоря, общие интерфейсы по умолчанию не являются ковариантными. Метод List<T>.Add - это то, что вызывает от DogGroomer (IAnimalGroomer<Dog>) от 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 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.

Ответ 5

По моему мнению, вы не можете поместить ограничения типа в параметр в этом случае. что означает, что вам может понадобиться сделать бокс и распаковку. вам может потребоваться использовать обычный интерфейс.

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

Или, может быть, вам понадобится другой технический проект для решения вашей проблемы.

Ответ 6

Вот какой код работает. Я добавил несколько классов и переключил 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);
  }
}

Пожалуйста, дайте мне знать, если есть какие-либо вопросы.

Ответ 7

Дело в том, что для ограничения типов используйте не общий интерфейс, а только общую версию.

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

Ответ 8

Я не могу использовать 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> в хэш-карту под ключом "собака". И запросите его снова с помощью ключа "собака", вы знаете, что он все равно будет 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 = ...;