简体   繁体   English

协变通用接口内的变体兼容 C# 事件

[英]Variant-compliant C# events inside a covariant generic interface

I have the following interfaces and delegate:我有以下接口和委托:

// Base devices, non-generic
public interface IDevice
{
    event DeviceVarChange VarChange;
}

public interface IVar
{
    // ...
}

public delegate void DeviceVarChange(IVar theVar);

I want to be able to create more specific interfaces with more specific event arguments and, at the same time, have them compatible and "variant-compliant" with the base interfaces above.我希望能够使用更具体的事件参数创建更具体的接口,同时让它们与上述基本接口兼容和“符合变体”。 For example:例如:

// Specific devices, non-generic
public interface ISpecificDevice : IDevice
{
    // Problem: I have my "VarChange" event, but it uses IVar!
    // I want it to use "ISpecificVar"!
}

public interface ISpecificVar
{
    // ...
}

So I went for a generic solution.所以我去找了一个通用的解决方案。 Since I want my subinterfaces to be "variant-compliant" and clients only "consumes" variables from my devices, I tried to use covariance:由于我希望我的子接口“符合变体”并且客户端只“消耗”我设备中的变量,因此我尝试使用协方差:

// Base devices, generic
public interface IDevice<out TVar> where TVar : IVar
{
    // Compilation error: TVar must be invariant but is covariant in this context
    event DeviceVarChange<TVar> VarChange;
}

public interface IVar
{
    // ...
}

// Problem: Delegate arguments can be contravariant, but not covariant
public delegate void DeviceVarChange<TVar>(TVar theVar) where TVar : IVar;

But I have the issue that delegate arguments cannot be covariant.但我有委托参数不能协变的问题。 Making the delegate arguments contravariant would resolve the compilation error, but then I would no longer be able to use a ISpecificDevice like a IDevice<IVar> for events (runtime error, delegates must be of the same type).使委托参数逆变将解决编译错误,但随后我将无法再使用ISpecificDevice类的IDevice<IVar>事件(运行时错误,委托必须是相同类型)。

public interface IDevice<out TVar> where TVar : IVar
{
    // No more compilation error, "DeviceVarChange" is contravariant
    event DeviceVarChange<TVar> VarChange;
}

public interface IVar
{
    // ...
}

public delegate void DeviceVarChange<in TVar>(TVar theVar) where TVar : IVar;

public interface ISpecificDevice : IDevice<ISpecificVar>
{
    // The event is using "ISpecificVar" here, as wanted
}

public interface ISpecificVar : IVar
{
    // ...
}

// + a "SpecificDevice" class implementing "ISpecificDevice"

// Try to use a specific instance as a base one
public class Test
{
    public void DoTest()
    {
        var genericDevice = GetSpecificDeviceAsBase();

        // Runtime error: delegates must be of the same type
        genericDevice.VarChange += OnVarChange;
    }

    public IDevice<IVar> GetSpecificDeviceAsBase()
    {
        return new SpecificDevice();
    }

    public void OnVarChange(IVar theVar)
    {
        // ...
    }
}

Is there a way out of this issue?有没有办法解决这个问题?

Or is using the first, non-generic solution and force explicit casts in the "specific" events the only way?还是使用第一个非通用解决方案并强制在“特定”事件中进行显式转换是唯一的方法?

I could as well go for a invariant generic DeviceVarChange<TVar> delegate, non-generic interfaces, and add a new event in every subinterfaces with the specific interface, but if I could dodge this, suggestions are welcome.我也可以选择一个不变的通用DeviceVarChange<TVar>委托、非通用接口,并在每个具有特定接口的子接口中添加一个新事件,但如果我能避免这种情况,欢迎提出建议。

Here is my current workaround as a first, hopefully transient answer.这是我目前的第一个解决方法,希望是暂时的答案。 This is what I will use while I hope that someone suggests me something better to work with.这就是我将使用的,同时我希望有人建议我使用更好的东西。

public interface IDevice
{
    event DeviceVarChange<IVar> VarChange;
}

public interface IVar
{
    // ...
}

public delegate void DeviceVarChange<TVar>(TVar theVar) where TVar : IVar;

public interface ISpecificDevice : IDevice
{
    // Overload the event inside the interface with the specific class
    new event DeviceVarChange<ISpecificVar> VarChange;
}

public interface ISpecificVar : IVar
{
    // ...
}

public class SpecificDevice : ISpecificDevice
{
    // Dear maintainer,
    // Forgive me.
    // Regards
    private void FireVarChange(ISpecificVar theVar)
    {
        lock (delegatesForVarChange)
        {
            foreach (var theDelegate in delegatesForVarChange)
            {
                theDelegate.Invoke(theVar);
            }
        }
    }

    private List<dynamic> delegatesForVarChange = new List<dynamic>();

    event DeviceVarChange<IVar> IDevice.VarChange
    {
        add
        {
            lock (delegatesForVarChange)
            {
                delegatesForVarChange.Add(value);
            }
        }
        remove
        {
            lock (delegatesForVarChange)
            {
                delegatesForVarChange.Remove(value);
            }
        }
    }

    public event DeviceVarChange<ISpecificVar> VarChange
    {
        add
        {
            lock (delegatesForVarChange)
            {
                delegatesForVarChange.Add(value);
            }
        }
        remove
        {
            lock (delegatesForVarChange)
            {
                delegatesForVarChange.Remove(value);
            }
        }
    }
}

// Try to use a specific instance as a base one
public class Test
{
    public void DoTest()
    {
        var genericDevice = GetSpecificDeviceAsBase();

        // Success
        genericDevice.VarChange += OnVarChange;
    }

    public IDevice GetSpecificDeviceAsBase()
    {
        return new SpecificDevice();
    }

    public void OnVarChange(IVar theVar)
    {
        // ...
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM