我是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: 很多有趣的讨论。我接受了一个答案,指出我在评论中动态调度。
答案 0 :(得分: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
然后回来问你有更多问题。
答案 1 :(得分: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>();
你的代码应该编译和工作。
答案 2 :(得分: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>
是协变的),你实际上也可以在列表中添加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
不同,问题不会发生。< / p>
答案 3 :(得分: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());
}
}
或者您可能需要另外一个技术设计来解决您的问题
答案 4 :(得分: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);
}
}
如果有任何问题,请告诉我
答案 5 :(得分:2)
正如Brian在上面的评论中指出的那样,也许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());
我不确定这是否与你所寻找的有关。希望它有所帮助!
答案 6 :(得分: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();
}
答案 7 :(得分:1)
我不习惯使用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>
放入键#34; dog&#34;下的hashmap中。并使用密钥&#34; dog&#34;再次请求它,您知道它仍然是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 = ...;