views:

140

answers:

2

In my application I am creating a simple event hub that offers something for registering subscribers:

Subscribes<EventType>(ISubscriber<EventType> subscriber) 
// and some other methods for adding subscribers

And for publishing events.

Publish<EventType>(EventType @event)

Quite simple. I want to route Publish<int>(0) to all subscribers implementing ISubscriber<int>.

What's not too hard is, that I want the subscribers EventType to be contravariant. So ISubscriber<object> should basically consume everything. Not shure wether I want them to consume valuetypes, too.

With C#4 that is no problem, but now I'm doing this stuff with C#3 and just faking contravariance with an interface:

public interface IContravariantGenerics { 
  object AsVariantFor(Type[] genericParamters);
}

Well, now, I want to empack data into "event types" like this. The generic parameters of that events must be covariant.

SubX : ISubscriber<DataChanged<A>>

DataChanged<T>
  T Data {get;}

When I publish Publish<DataChanged<B>>(new DataChanged<B>(new B()) (given B : A), the Subscriber should be notified with DataChanged<A> where Data is the B-instance passed to DataChanged<B>. So I need covariance support as well.

I thought of writing a lib that supports both Co-/And Contravariance like this:

IMyObject<T1, T2> : IWithVariance<In, Out>

Which would allow conversions (not casts!) like this:

Obj<Fruit, Fruit> x;
IMyObject<Apple, object> x2 = x.ToVariant<Apple, object>();

What do you think? Is it possible? Thought of doing it using dynamic proxies.

+1  A: 

IMO, this will make things very complex very quickly, and it'll mean you end up writing a lot of reflection code. Can you wait for C# 4.0? ;-p

Alternatively, your code could just ignore things it doesn't know how to handle...

Marc Gravell
Right, I would have to assume stuff here and there. In cases of Ambiguity I would have to throw exceptions.
Lars Corneliussen
+1  A: 

In this part:

When I publish Publish<DataChanged<B>>(new DataChanged<B>(new B()), the Subscriber should be notified with DataChanged<A> where .Data is the B-instance.

I may not have understood you - I can't see what .Data refers to, for example, and I can only guess at the relationship between B and A. Do you mean that B is derived from A?

If so, C# 4 will not necessarily make such a thing happen automatically. The types X<A> and X<B> are not compatible at all by default. If X is an interface and the type parameter is marked as out, then X<B> can be assigned to a variable of type X<A>. But note that this is for interfaces only, not concrete types (there is a similar provision for delegates, but that's all).

Edit:

So therefore what you want to do is simulate the way that X<B> can be assigned to a variable of type X<A> in C#/CLR 4.0, where X is an interface.

Suppose X is:

interface X<T>
{
    T Foo(int arg);

    // Note: T may only appear as an output, so this is illegal:
    // void Foo(T arg);
}

You have an X<B>, you need an X<A>. You know that B is assignable to A. So you need the following adaptor:

class WrapX_A_B : X<A>
{
    public X<B> Impl { get; set; }

    public A Foo(int arg)
    {
        return Impl.Foo(arg);
    }
}

You just forward each method on to the real implementation.

However, you would need such a wrapper class for every possible combination of generic outer classes and pairs of generic parameters related by inheritance. It would be a tedious, error-prone and never-complete task to write them all by hand and maintain a big lookup to pick the right one for a given situation.

So now you're into code generation to manufacture the wrapper classes at runtime.

Daniel Earwicker
You're right. Corrected the question.In C#4 I would end up with ISubscriber<in T> and IChangedEvent<out T>.
Lars Corneliussen
See update above.
Daniel Earwicker