4

I've always been under impression that the C# compiler is able to infer type params in cases like the following:

class Program
{
    static void Main(string[] args)
    {
        IMessageBus messageBus = null;

       //Here the compiler nags "type params for Publish cannot be inferred from the usage.. .."
        messageBus.Publish(new CorrectorAdded(10));
    }
}

public interface IEvent<out TPayload>
{
    TPayload Payload { get; }
}

public abstract class EventBase<TPayload> : IEvent<TPayload>
{
    public TPayload Payload { get; private set; }

    protected EventBase(TPayload payload)
    {
        Payload = payload;
    }
}

public interface IMessageBus
{
    void Publish<TEvent, TPayload>(TEvent @event) where TEvent : IEvent<TPayload>;

    IDisposable Subscribe<TEvent, TPayload>(Action<TPayload> listener) where TEvent : IEvent<TPayload>;
}

public class CorrectorAdded : EventBase<CorrectorAddedArgs>
{
    public CorrectorAdded(CorrectorAddedArgs payload) : base(payload)
    {
    }

    public CorrectorAdded(int correctorId) : this(new CorrectorAddedArgs(correctorId))
    {
    }
}

public class CorrectorAddedArgs
{
    public int CorrectorId { get; private set; }

    public CorrectorAddedArgs(int correctorId)
    {
        CorrectorId = correctorId;
    }
}

Any ideas on why this is happening and how to get the type inference to work in that case?

Thanks.

Ant
  • 181
  • 2
  • 14
  • 2
    Possible duplicate of http://stackoverflow.com/questions/6630690/why-cant-nested-generic-types-be-inferred (not the exact same question, yet the answer by Eric Lippert applies to your question as well). – haim770 Nov 09 '13 at 20:25

2 Answers2

3

The interface signature for publish defines 2 constraints:

void Publish<TEvent, TPayload>(TEvent @event) where TEvent : IEvent<TPayload>;
  1. TEvent which will be the signature for the method parameter
  2. TPayload which states TEvent must implement the interface IEvent<TPayload>

If the Publish method only had the constraint of TEvent, then the compiler could infer the usage since the signature of the method already has the type constraint defined, i.e.,

void Publish<TEvent>(TEvent @event);

The above would then allow for the method invocation to be used without the type constraint, i.e.,

messageBus.Publish( new CorrectorAdded( 10 ) );
// would be the same as
messageBus.Publish<CorrectorAdded>( new CorrectorAdded( 10 ) );

However, since the interface defines the 2nd constraint, the compiler has no idea what the intent is for TPayload since the event can implement any number of IEvent<TPayload>'s. However, if the method signature did include the type of TPayload then the complier could actual infer the constraint, i.e.,

void Publish<TEvent, TPayload>(TEvent @event, TPayload payload) where TEvent : IEvent<TPayload>;

And then then method could be invoked without the type constraints:

messageBus.Publish( new CorrectorAdded( 10 ), new FooThatImplementsTPayload() );
Metro Smurf
  • 37,266
  • 20
  • 108
  • 140
  • 1
    Thanks! That cleared all up. Didn't make my life easier, though as the API does look ambiguous this way =). Will need to turn smth around maybe.. – Ant Nov 09 '13 at 20:43
0

To those who are still curious about how an alternative API might look like, here is what I came up with:

class Program
{
    static void Main(string[] args)
    {
        IMessageBus messageBus = null;

        messageBus
            .Event<CorrectorAdded>()
            .Subscribe(eventArgs => { /*skipped*/ });

        //skipped

        messageBus
            .Event<CorrectorAdded>()
            .Publish(new CorrectorAddedArgs(1));
    }
}

public abstract class EventBase
{
    //skipped
}

public abstract class EventBase<TPayload> : EventBase
{
    public IDisposable Subscribe(Action<TPayload> listener)
    {
        //skipped
    }

    public void Publish(TPayload payload)
    {
        //skipped
    }
}

public class CorrectorAdded : EventBase<CorrectorAddedArgs>
{
}

public class CorrectorAddedArgs
{
    public int CorrectorId { get; private set; }

    public CorrectorAddedArgs(int correctorId)
    {
        CorrectorId = correctorId;
    }
}

public interface IMessageBus
{
    TEvent Event<TEvent>() where TEvent : EventBase, new();
}
Ant
  • 181
  • 2
  • 14