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).
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.
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)
{
// ...
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.