c# - Why doesn't an interface work but an abstract class does with a generic class constraint? -
the code below shows generic class type constraint (pub<t>
). class has event can raise allowing pass message subscribers. constraint message must implement imsg
(or inherit imsg
when it's abstract class).
pub<t>
provides subscribe
method allow objects subscribe notify
event if , if object implements ihandler<imsg>
.
using .net 4, code below shows error on baseimplementer.notifyeventhandler
stating that:
"no overload 'ihandler<imsg>.notifyeventhandler(imsg)' matches delegate 'system.action<t>'"
question: (with updated subscribe method)
why error go away change `imsg` abstract class instead of interface?
public interface imsg { } // doesn't work //public abstract class imsg { } // work public class msg : imsg { } public class pub<t> t : imsg { public event action<t> notify; public void subscribe(object subscriber) { // subscriber subscribes if implements ihandler of exact same type t // compiles , works ihandler<t> implementer = subscriber ihandler<t>; if (implementer != null) this.notify += implementer.notifyeventhandler; // if subscriber implements ihandler<imsg> subscribe notify (even if t msg because msg implements imsg) // not compile if imsg interface, if imsg abstract class ihandler<imsg> baseimplementer = subscriber ihandler<imsg>; if (baseimplementer != null) this.notify += baseimplementer.notifyeventhandler; } } public interface ihandler<t> t : imsg { void notifyeventhandler(t data); }
code below here not necessary reproduce issue... shows how code above might used. imsg
(and derived msg
) classes define or implement methods called in 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 ihandler<msg>).notifyeventhandler; this.pub.notify += (this.subb ihandler<imsg>).notifyeventhandler; //i want call... this.pub.subscribe(this.suba); this.pub.subscribe(this.subb); //...except subscribe method wont build when imsg interface } }
why error go away change
imsg
abstract class instead of interface?
good question!
the reason fails because relying upon formal parameter contravariance in conversion method group delegate type, covariant , contravariant method group conversions delegates legal when every varying type known reference type.
why varying type not "known reference type"? because an interface constraint on t not constrain t reference type. constrains t type implements interface, struct types can implement interfaces too!
when make constraint abstract class instead of interface compiler knows t has reference type, because reference types can extend user-supplied abstract classes. compiler knows variance safe , allows it.
let's @ simpler version of program , see how goes wrong if allow conversion want:
interface imsg {} interface ihandler<t> t : imsg { public void notify(t t); } class pub<t> t : imsg { public static action<t> makesomeaction(ihandler<imsg> handler) { return handler.notify; // why illegal? } }
that's illegal because 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, think does. on caller side, action expecting put 24 byte struct s on call stack, , expecting callee process it. callee, handler.notify, expecting 4 or 8 byte reference heap memory on stack. we've misaligned stack between 16 , 20 bytes, , first field or 2 of struct going interpreted pointer memory, crashing runtime.
that's why illegal. struct needs boxed before action processed, did supply code boxes struct!
there 3 ways make work.
first, if guarantee reference type works out. can either make imsg class type, thereby guaranteeing derived type reference type, or can put "class" constraint on various "t"s in program.
second, can use t consistently:
class pub<t> t : imsg { public static action<t> makesomeaction(ihandler<t> handler) // t, not imsg { return handler.notify; } }
now cannot pass handler<imsg>
c<smsg>.makesomeaction
-- can pass handler<smsg>
, such notify method expects struct passed.
third, can write code boxing:
class pub<t> t : imsg { public static action<t> makesomeaction(ihandler<imsg> handler) { return t => handler.notify(t); } }
now compiler sees, ah, doesn't want use handler.notify directly. rather, if boxing conversion needs happen intermediate function take care of it.
make sense?
method group conversions delegates have been contravariant in parameter types , covariant in return types since c# 2.0. in c# 4.0 added covariance , contravariance on conversions on interfaces , delegate types marked being safe variance. seems sorts of things doing here possibly using these annotations in interface declarations. see long series on design factors of feature necessary background. (start @ bottom.)
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/
incidentally, if try pull these sorts of conversion shenanigans in visual basic, cheerfully allow to. vb equivalent of last thing; detect there type mismatch , rather telling can fix it, silently insert different delegate on behalf fixes types you. on 1 hand, nice sort of "do mean not say" feature, in code looks ought work works. on other hand, rather unexpected ask delegate made out of method "notify", , delegate out bound completely different method proxy "notify".
in vb, design philosophy more on "silently fix mistakes , meant" end of spectrum. in c# design philosophy more on "tell me mistakes can decide how fix them myself" end. both reasonable philosophies; if sort of person likes when compiler makes guesses you, might consider looking vb. if you're sort of person likes when compiler brings problems attention rather making guess meant, c# might better you.
Comments
Post a Comment