Mixing covariance and contravariance

Purpose:

Iterating over the next collection

var collection = new IImportTrigger<EventArgs>[]
{
    new FileSystemImportTrigger()
    , new TimerImportTrigger()
};

      

thus

foreach (var trigger in collection)
{
    trigger.Import += trigger.OnImport;
}

      

This is what I still have

public delegate void ImportTriggerEventHandler<in T>(object sender, T args) where T : EventArgs;

public interface IImportTrigger<out T> where T : EventArgs
{
    event ImportTriggerEventHandler<T> Import;
    void OnImport<T1>(object sender, T1 args) where T1 : EventArgs;
}

public class FileSystemImportTrigger : IImportTrigger<FileSystemEventArgs>
{
    public event ImportTriggerEventHandler<FileSystemEventArgs> Import;

    public void OnImport<T>(object sender, T args) where T : EventArgs {  }
}

public class TimerImportTrigger : IImportTrigger<ElapsedEventArgs>
{
    public event ImportTriggerEventHandler<ElapsedEventArgs> Import;

    public void OnImport<T>(object sender, T args) where T : EventArgs {  }
}

      

expectations:

I would like to define an IImportTrigger with only one common parameter.

Problem:

If I change the interface definition to the following (note that the generic argument T is no longer a covariant).

public interface IImportTrigger<T> where T : EventArgs
{
    event ImportTriggerEventHandler<T> Import;
    void OnImport(object sender, T args);
}

      

and therefore

public class FileSystemImportTrigger : IImportTrigger<FileSystemEventArgs>
{
    public event ImportTriggerEventHandler<FileSystemEventArgs> Import;

    public void OnImport(object sender, FileSystemEventArgs args) { }
}

public class TimerImportTrigger : IImportTrigger<ElapsedEventArgs>
{

    public event ImportTriggerEventHandler<ElapsedEventArgs> Import;

    public void OnImport(object sender, ElapsedEventArgs args) { }
}

      

I will not be able to create a generic type for my collection

var collection = new IImportTrigger<EventArgs>[]
{
    new FileSystemImportTrigger()
    , new TimerImportTrigger()
};

      

because the Generic parameter is no longer displayed.

Question:

Is there a way to accomplish my scenario?

+3


source to share


1 answer


Switching OnImport so that it is not generic at all, use an explicit interface, then create another derived interface that is not covariant, which has a generic OnImport UI that you could disable.

internal class Program
{
    private static void Main(string[] args)
    {
        var collection = new IImportTriggerBase<EventArgs>[]
        {
            new FileSystemImportTrigger()
            , new TimerImportTrigger()
        };

        foreach (var trigger in collection)
        {
            trigger.Import += trigger.OnImport;
        }
    }
}

public delegate void ImportTriggerEventHandler<in T>(object sender, T args) where T : EventArgs;

public interface IImportTriggerBase<out T> where T : EventArgs
{
    event ImportTriggerEventHandler<T> Import;
    void OnImport(object sender, EventArgs args);
}

public interface IImportTrigger<T> : IImportTriggerBase<T> where T : EventArgs
{
    void OnImport(object sender, T args);
}

public class FileSystemImportTrigger : IImportTrigger<FileSystemEventArgs>
{
    public event ImportTriggerEventHandler<FileSystemEventArgs> Import;

    public void OnImport(object sender, FileSystemEventArgs args) { }

    void IImportTriggerBase<FileSystemEventArgs>.OnImport(object sender, EventArgs args)
    {
        OnImport(sender, (FileSystemEventArgs)args);
    }
}

public class TimerImportTrigger : IImportTrigger<ElapsedEventArgs>
{
    public event ImportTriggerEventHandler<ElapsedEventArgs> Import;

    public void OnImport(object sender, ElapsedEventArgs args) { }

    void IImportTriggerBase<ElapsedEventArgs>.OnImport(object sender, EventArgs args)
    {
        OnImport(sender, (ElapsedEventArgs)args);
    }
}

      

However, this gives you an extra jerk of the method OnImport(object sender, EventArgs args)

that maps to IImportTrigger<T>

.




This should have solved your problem, if I'm going to do it and I'm guessing you just want derived classes to be able to pick up on the fact that it Import

is being run and you don't actually need OnImport I would just do

internal class Program
{
    private static void Main(string[] args)
    {
        var collection = new IImportTrigger<EventArgs>[]
        {
            new FileSystemImportTrigger()
            , new TimerImportTrigger()
        };
    }
}

public delegate void ImportTriggerEventHandler<in T>(object sender, T args) where T : EventArgs;

public interface IImportTrigger<out T> where T : EventArgs
{
    event ImportTriggerEventHandler<T> Import;
}

public abstract class OnImportBase<T> : IImportTrigger<T> where T: EventArgs
{

    public event ImportTriggerEventHandler<T> Import;

    protected virtual void OnImport(object sender, T args)
    {
        var tmp = Import;
        if (tmp != null)
        {
            tmp(this, args);
        }
    }
}

public class FileSystemImportTrigger : OnImportBase<FileSystemEventArgs>
{
    protected override void OnImport(object sender, FileSystemEventArgs args)
    {
        DoSomeExtraStuffBeforeImport();
        base.OnImport(sender, args);
    }

    private void DoSomeExtraStuffBeforeImport()
    {
    }
}

public class TimerImportTrigger : OnImportBase<ElapsedEventArgs>
{
    protected override void OnImport(object sender, ElapsedEventArgs args)
    {
        base.OnImport(sender, args);
        DoSomeExtraStuffAfterImport();
    }

    private void DoSomeExtraStuffAfterImport()
    {
    }
}

      

This gets rid of the event subscription and instead treats it as an override (which is a common pattern in .NET events).

+2


source







All Articles