1

I have a .net assembly that exposes a public class (named A) to be created other programs using reflection (Assembly.CreateInstance). For now, it works fine.

But now I have two subclasses of A, say A1 and A2, and I need to create one or the other, depending on a runtime check. The client cannot be modified, it should believe that the object returned is of class A.

If I were using COM instead of .net, it would be easy enough: simply add the check into the IClassFactory.CreateInstance and be done. But is there a way to do this in .net?

Note: the obvious solution of using a proxy object, or similar, is not practical, because class A has several hundred methods (!) and I don't want to reroute all of them.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Also, I wouldn't say that the "several hundred method" is a reason not to use an abstraction such as an interface - although it is perhaps a reason to not have several hundred methods ;p – Marc Gravell Aug 12 '11 at 09:19
  • Lie is what I need! The problem is that the client is not written by me. Actually it is a library code that exposes my object using a different 'unified' interface to another program (not mine either). So the only place where I can decide what to do is. – rodrigo Aug 12 '11 at 09:30

1 Answers1

2

Firstly, it should be noted that it would be easier if your client could use a factory method that you provide rather than having to hijack Activator.CreateInstance. Please consider that fully before reading on...

Here's an example using ContextBoundObject - it uses a counter as the "runtime check" to decide between B and C as the actual object, even though the caller asked for an A. Note that this also works if they used new A().

private static void Main()
{
    // subvert Activator.CreateInstance
    for(int i = 0 ; i < 100 ; i++)
    {
        object obj = Activator.CreateInstance(typeof (A));
        Console.WriteLine(obj.GetType().Name);
    }
    // subvert "new()"
    for(int i = 0 ; i < 100 ; i++)
    {
        object obj = new A();
        Console.WriteLine(obj.GetType().Name);
    }
}

class AProxyAttribute : ProxyAttribute
{
    int flipper;
    public override MarshalByRefObject CreateInstance(Type serverType)
    {
        if (serverType == typeof(A))
        {
            return ((flipper++)%2) == 0 ? (A) new B() : (A) new C();
        }
        return base.CreateInstance(serverType);
    }
}
[AProxy]
class A : ContextBoundObject { }
// the two subclasses
class B : A {}
class C : A{}

So yes, you can be dastardly and lie to the caller by giving them back something other than they asked for ;p This is fairly evil code, though. YMMV etc.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Looks nice, but ProxyAttribute is only for remoting, isn't it? The client code is doing Assembly.CreateInstance(...) directly, with reflection, not remoting. Would it still work? Because it tells me Cannot implicitly convert type 'B' to 'System.MarshalByRefObject'. – rodrigo Aug 12 '11 at 09:32
  • @rodrigo the above example doesn't involve remoting... so no, this isn't related to remoting. It *is*, however, tied to `ContextBoundObject`, hence why `A : ContextBoundObject`. It will only work if you inherit from `ContextBoundObject`. – Marc Gravell Aug 12 '11 at 09:35
  • Oh, I see, A must inherit from ContextBoundObject. But A already has a base class. It must, because the client code expects that. How much I miss multiple inheritance! – rodrigo Aug 12 '11 at 09:36
  • @rodrigo as far as I know, this is the *only* way to subvert `Activator.CreateInstance` (and `new()`) - so: either make the base-class inherit from `ContextBoundObject`, or accept that this isn't an available option. – Marc Gravell Aug 12 '11 at 09:38
  • Note this also ties into an evil example of `null`-ness: http://stackoverflow.com/questions/194484/whats-the-strangest-corner-case-youve-seen-in-c-or-net/194671#194671 – Marc Gravell Aug 12 '11 at 11:23
  • Your solution is cool, but sadly not applicable to my case. I'm afraid that I'm stuck to my several-hundred-functions proxy object. Thank you anyway. – rodrigo Aug 12 '11 at 11:53