本文介绍了为什么没有一个接口工作,而是一个抽象类与泛型类的限制呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 下面的代码显示了一个类型约束泛型类(酒吧< T> )。类具有可以提高使我们能够传递消息给用户的事件。约束是消息必须实施 IMSG (或 IMSG 继承时,它是一个抽象类)。 酒吧< T> 还提供了一个订阅方法,以允许对象订阅通知事件当且仅当对象实现 IHandler< IMSG方式> 使用.NET 4,下面的代码显示,指出对 baseImplementer.NotifyEventHandler 错误:结果 无过载IHandler< IMSG> .NotifyEventHandler(IMSG)'匹配委托System.Action< T>' 问:(使用更新的订阅方法) 为什么错误,只要我改变`IMsg`一个抽象类,而不是一个接口离开 公共接口IMSG {} //不行的 //公共抽象类IMSG {} //没有工作 酒店的公共类信息:IMSG {} 公共类酒吧< T>其中T:IMSG {公共事件动作< T>通知; 公共无效订阅(目标用户) {$如果它实现了完全相同的类型为T 的IHandler b $ B //订阅服务器订阅//这总是编译和作品 IHandler< T>实施者=用户为IHandler< T> ;; 如果(实施者!= NULL) this.notify + = implementer.NotifyEventHandler; //如果用户实现IHandler< IMSG>订阅通知(即使T是因为消息消息实现IMSG) //如果IMSG是一个接口,只有IMSG是一个抽象类 IHandler<这并不编译; IMSG> baseImplementer =用户为IHandler< IMSG取代; 如果(!baseImplementer = NULL) this.notify + = baseImplementer.NotifyEventHandler; } } 公共接口IHandler< T>其中T:IMSG {无效NotifyEventHandler(T数据); } 下面的代码在这里没有必要来重现问题...但显示了如何上面的代码可能会被使用。显然 IMSG (和衍生消息)班将定义或实现,可以在处理程序中调用的方法。 公共类苏巴:IHandler<&消息GT; {无效IHandler<消息> .NotifyEventHandler(消息数据){} } 公共类SUBB:IHandler< IMSG> {无效IHandler< IMSG> .NotifyEventHandler(IMSG数据){} } 类MyClass的 {酒吧<消息>酒馆=新的酒吧<&消息GT;(); 苏巴苏巴=新舒伯(); SUBB subB的=新SUBB(); 公共MyClass的() { //而不是调用... this.pub.notify + =(this.subA作为IHandler<&消息GT;) .NotifyEventHandler; this.pub.notify + =(this.subB作为IHandler< IMSG>)NotifyEventHandler。 //我要打电话... this.pub.Subscribe(this.subA); this.pub.Subscribe(this.subB); //...except的订阅方法不会建立在IMSG是一个接口} } 解决方案 为什么错误,只要我换走 IMSG 来一个抽象类,而不是一个接口? 好问题! 这失败的原因是因为你是在形式参数逆变中的方法组转换靠到在委托类型,但的协变和逆变的方法组转换来时的每一个不同的类型被称为是引用类型。 为什么变化的类型不知道是引用类型?因为对T的接口限制不也限制了T可为引用类型即可。它限制了T可为任何类型实现接口,但结构类型也能实现接口! 当你做出一个抽象类,而不是接口,那么编译器知道T有是一个引用类型,因为只有引用类型可以延长约束用户提供抽象类。然后,编译器知道方差是安全的,并允许它。 让我们看看你的程序更简单的版本,看看它是如何去错了,如果你允许的转换你想要的: 接口IMSG {} 接口IHandler< T>其中T:IMSG {公共无效通知(T T); } 类酒吧< T>其中T:IMSG {公共静态动作< T> MakeSomeAction(IHandler< IMSG>处理) {返回handler.Notify; //这是为什么非法的? } } 这是非法的,因为你可以接着说: 结构SMSG:IMSG {公众诠释A,b,C,X,Y,Z; } 级处理器:IHandler< IMSG> {公共无效通知(IMSG MSG) {} } ... 动作<&SMSG GT;行动=酒吧和LT; SMSG> .MakeSomeAction(新的处理程序()); 动作(默认值(SMSG)); OK,现在想想是什么一样。在发送方,动作期待把一个24字节的struct调用堆栈,并期待被调用来处理它。被叫方,Handler.Notify,期待一个四个或八个字节引用堆内存是在堆栈中。我们刚刚由16和20字节之间,并且第一场或两个该结构的将被解释为指向存储器,崩溃运行时未对准堆栈 这就是为什么这是非法的。该结构需要处理的动作之前盒装,但无处你提供任何代码框的结构! 有三种方法来完成这项工作。 首先,如果你保证,一切都是引用类型,那么这是可行的。你可以让IMSG类类型,从而保证任何派生类型是引用类型,或者你可以把你的程序的各种TS类的约束。 其次,你还可以使用t一致: 类酒吧< T>其中T:IMSG {公共静态动作< T> MakeSomeAction(IHandler< T>处理)// T,不IMSG {返回handler.Notify; } } 现在,你可以不通过处理器< IMSG> 到 C< SMSG> .MakeSomeAction - 你只能通过一个处理< SMSG> ,使得它的通知方法要求将通过结构 三,你可以写代码,做拳击: 类酒吧< T>其中T:IMSG {公共静态动作< T> MakeSomeAction(IHandler< IMSG>处理) {返回T => handler.Notify(T); } } 现在的编译器看到啊,他不想要直接使用handler.Notify。相反,如果一个拳击转换需要再发生中间功能将照顾它。 请有意义吗? 方法组转换为代表以来已C#2.0逆变他们的参数类型和协变他们的返回类型。在C#4.0,我们还增加了协变和逆变上被标记为方差安全接口和委托类型转换。这似乎是从各种各样的事情,你在这里做了你可能在你的界面声明中使用这些注解。请参阅此功能的必要背景的设计因素,我长的系列。 (开始在底部。) 的 http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/ 顺便说一句,如果你试图去拉这些类型在Visual Basic中转换的恶作剧,它会高高兴兴让你。 VB会做的最后一件事同等学历;它会检测有一个类型不匹配而非告诉你关于它,以便您可以修复它,它会悄悄地插入的不同的委托的代表你为你解决了类型。在一方面,这是一个很好的那种做我的意思不是我说的话的功能,在该代码看起来像它应该只是工作的作品。在另一方面,这是相当意外的,你问一个委托进行了通知的方法,而你回来了委托绑定到的完全不同的方法的是一个代理的通知。 在VB中,设计理念更是对默默修复自己的错误,做我的意思是频谱结束。在C#中的设计理念更是对告诉我关于我的错误,所以我可以决定如何解决这些问题自己结束。两者都是合理的理念;如果你是何许人时,编译器是一个很好的猜测为你喜欢的,你可以考虑寻找到VB。如果你是何许人时,编译器带来的问题,你的注意力,而不是拍一部关于你的意思,C#可能是你最好的猜测谁喜欢它。 The code below shows a generic class with a type constraint (Pub<T>). The class has an event that it can raise allowing us to pass a message to subscribers. The constraint is that the message must implement IMsg (or inherit from IMsg when it's is an abstract class).Pub<T> also provides a Subscribe method to allow objects to subscribe to the notify event if and only if the object implements IHandler<IMsg>.Using .NET 4, the code below shows an error on baseImplementer.NotifyEventHandler stating that:"No overload for 'IHandler<IMsg>.NotifyEventHandler(IMsg)' matches delegate 'System.Action<T>'"Question: (with updated Subscribe method)Why does the error go away as soon as I change `IMsg` to an abstract class instead of an interface?public interface IMsg { } // Doesn't work//public abstract class IMsg { } // Does workpublic class Msg : IMsg { }public class Pub<T> where T : IMsg{ public event Action<T> notify; public void Subscribe(object subscriber) { // Subscriber subscribes if it implements IHandler of the exact same type as T // This always compiles and works IHandler<T> implementer = subscriber as IHandler<T>; if (implementer != null) this.notify += implementer.NotifyEventHandler; // If subscriber implements IHandler<IMsg> subscribe to notify (even if T is Msg because Msg implements IMsg) // This does not compile if IMsg is an interface, only if IMsg is an abstract class IHandler<IMsg> baseImplementer = subscriber as IHandler<IMsg>; if (baseImplementer != null) this.notify += baseImplementer.NotifyEventHandler; }}public interface IHandler<T> where T : IMsg{ void NotifyEventHandler(T data);}Code below here is not necessary to reproduce the issue... but shows how the code above might be used. Obviously IMsg (and the derived Msg) classes would define or implement methods that could be called in a handler.public class SubA : IHandler<Msg>{ void IHandler<Msg>.NotifyEventHandler(Msg data) { }}public class SubB : IHandler<IMsg>{ void IHandler<IMsg>.NotifyEventHandler(IMsg data) { }}class MyClass{ Pub<Msg> pub = new Pub<Msg>(); SubA subA = new SubA(); SubB subB = new SubB(); public MyClass() { //Instead of calling... this.pub.notify += (this.subA as IHandler<Msg>).NotifyEventHandler; this.pub.notify += (this.subB as IHandler<IMsg>).NotifyEventHandler; //I want to call... this.pub.Subscribe(this.subA); this.pub.Subscribe(this.subB); //...except that the Subscribe method wont build when IMsg is an interface }} 解决方案 Why does the error go away as soon as I change IMsg to an abstract class instead of an interface?Good question! The reason this fails is because you are relying upon formal parameter contravariance in the conversion from the method group to the delegate type, but covariant and contravariant method group conversions to delegates are only legal when every varying type is known to be a reference type. Why is the varying type not "known to be a reference type"? Because an interface constraint on T does not also constrain T to be a reference type. It constrains T to be any type that implements the interface, but struct types can implement interfaces too! When you make the constraint an abstract class instead of an interface then the compiler knows that T has to be a reference type, because only reference types can extend user-supplied abstract classes. The compiler then knows that the variance is safe and allows it.Let's look at a much simpler version of your program and see how it goes wrong if you allow the conversion you want:interface IMsg {}interface IHandler<T> where T : IMsg{ public void Notify(T t);}class Pub<T> where T : IMsg{ public static Action<T> MakeSomeAction(IHandler<IMsg> handler) { return handler.Notify; // Why is this illegal? }}That's illegal because you could then say:struct SMsg : IMsg { public int a, b, c, x, y, z; }class Handler : IHandler<IMsg> { public void Notify(IMsg msg) { }}...Action<SMsg> action = Pub<SMsg>.MakeSomeAction(new Handler());action(default(SMsg));OK, now think about what that does. On the caller side, the action is expecting to put a 24 byte struct S on the call stack, and is expecting the callee to process it. The callee, Handler.Notify, is expecting a four or eight byte reference to heap memory to be on the stack. We've just misaligned the stack by between 16 and 20 bytes, and the first field or two of the struct is going to be interpreted as a pointer to memory, crashing the runtime.That's why this is illegal. The struct needs to be boxed before the action is processed, but nowhere did you supply any code that boxes the struct!There are three ways to make this work.First, if you guarantee that everything is a reference type then it all works out. You can either make IMsg a class type, thereby guaranteeing that any derived type is a reference type, or you can put the "class" constraint on the various "T"s in your program.Second, you can use T consistently:class Pub<T> where T : IMsg{ public static Action<T> MakeSomeAction(IHandler<T> handler) // T, not IMsg { return handler.Notify; }}Now you cannot pass a Handler<IMsg> to C<SMsg>.MakeSomeAction -- you can only pass a Handler<SMsg>, such that its Notify method expects the struct that will be passed.Third, you can write code that does boxing:class Pub<T> where T : IMsg{ public static Action<T> MakeSomeAction(IHandler<IMsg> handler) { return t => handler.Notify(t); }}Now the compiler sees, ah, he doesn't want to use handler.Notify directly. Rather, if a boxing conversion needs to happen then the intermediate function will take care of it.Make sense?Method group conversions to delegates have been contravariant in their parameter types and covariant in their return types since C# 2.0. In C# 4.0 we also added covariance and contravariance on conversions on interfaces and delegate types that are marked as being safe for variance. It seems like from the sorts of things you are doing here that you could possibly be using these annotations in your interface declarations. See my long series on the design factors of this feature for the necessary background. (Start at the bottom.)http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/Incidentally, if you try to pull these sorts of conversion shenanigans in Visual Basic, it will cheerfully allow you to. VB will do the equivalent of the last thing; it will detect that there is a type mismatch and rather than telling you about it so that you can fix it, it will silently insert a different delegate on your behalf that fixes up the types for you. On the one hand, this is a nice sort of "do what I mean not what I say" feature, in that code that looks like it ought to work just works. On the other hand, it is rather unexpected that you ask for a delegate to be made out of the method "Notify", and the delegate you get back out is bound to a completely different method that is a proxy for "Notify". In VB, the design philosophy is more on the "silently fix my mistakes and do what I meant" end of the spectrum. In C# the design philosophy is more on the "tell me about my mistakes so I can decide how to fix them myself" end. Both are reasonable philosophies; if you are the sort of person that likes when the compiler makes good guesses for you, you might consider looking into VB. If you're the sort of person who likes it when the compiler brings problems to your attention rather than making a guess about what you meant, C# might be better for you. 这篇关于为什么没有一个接口工作,而是一个抽象类与泛型类的限制呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 10-16 07:19