Reputation: 631
I want to raise an event that will allow an object of type Widget
(or any derived class) to be returned, with the specific type being defined via generics.
public class WidgetProcessor
{
public event EventHandler<WidgetRequiredEventArgs<Widget>> WidgetRequired;
public void DoSomethingThatNeedsAWidget<T>() where T: Widget
{
Widget widget = OnWidgetRequired<T>();
//...now do something with the widget
}
private T OnWidgetRequired<T>() where T: Widget
{
T widget = null;
if (this.WidgetRequired != null)
{
WidgetRequiredEventArgs<T> e = new WidgetRequiredEventArgs<T>();
this.WidgetRequired(this, e);
widget = e.Widget;
}
return widget;
}
}
public class WidgetRequiredEventArgs<T>
: EventArgs where T : Widget
{
public WidgetRequiredEventArgs()
{
}
public T Widget { get; set; }
}
Constraints on OnWidgetRequired<T>()
and DoSomethingThatNeedsAWidget<T>()
allow me to limit the specified type to Widget
or a derived class. Ideally, I'd do the same for the event declaration, but it doesn't support the use of <T>
with a constraint for the event args, so I've had to declare it explicitly as Widget
.
However, this gives the compile-time error:
CS1503: Argument 2: cannot convert from 'WidgetRequiredEventArgs<T>' to 'WidgetRequiredEventArgs<Widget>'
for the e
argument on the line:
this.WidgetRequired(this, e);
So why don't the constraints on OnWidgetRequired<T>()
and WidgetRequiredEventArgs<T>
satisfy the eventhandler's type definition, and how can I get it to compile?
Upvotes: 2
Views: 158
Reputation: 70652
You have a fundamental problem in your example. You want for subscribers to the event to be able to produce the object type that you specify in your generic method. I.e. your method creates the args object, the handler sets the property, the method raises the property, and then the method gets the property value.
But if you could do what you're asking, then producers could set whatever type of Widget
they are designed to handle, even if it's not the type of Widget
that the method was being called to retrieve.
Or put another way, the property's generic type cannot be variant, because it's legal to only be covariant or contravariant, not both at the same time.
One approach would be to move the event itself into a generic type, rather than using a generic method. This allows the event to be declared using the correct type at every step of the way. The framework will instantiate a new concrete type, with a separate event, for each generic type parameter used in the code.
For example, here's a simple generic implementation (I've renamed the class names from Widget
to something more readable, so it's more clear the relationship between types, and I've made everything static
for simplicity...of course, you could make these non-static if you had a scenario where it made sense to have more than one event using the same generic type parameter):
class Base { }
class Derived1 : Base { }
class Derived2 : Base { }
class BaseEventArgs<T> : EventArgs where T : Base
{
public T Item { get; set; }
}
static class ItemHandler<T> where T : Base
{
public static event EventHandler<BaseEventArgs<T>> ItemProducer;
public static T OnBaseRequired()
{
BaseEventArgs<T> args = new BaseEventArgs<T>();
ItemProducer?.Invoke(null, args);
return args.Item;
}
}
Then you just use it something like this:
public void DoSomethingThatNeedsABase<T>() where T : Base
{
Base widget = ItemHandler<T>.OnBaseRequired();
//...now do something with the "Base", aka "widget
}
Upvotes: 0
Reputation: 141835
Classes in C# does not allow variance (and for good reasons). You can try introducing an covariant interface for you event args if that will suit your use case:
public interface IWidgetRequiredEventArgs<out T> where T : Widget
{
public T Widget { get; }
}
public class WidgetRequiredEventArgs<T>
: EventArgs, IWidgetRequiredEventArgs<T> where T : Widget
{
public WidgetRequiredEventArgs()
{
}
public T Widget { get; set; }
}
And use it for event handler:
public event EventHandler<IWidgetRequiredEventArgs<Widget>> WidgetRequired;
Or you can make the WidgetProcessor
generic:
public class WidgetProcessor<T> where T: Widget
{
public event EventHandler<WidgetRequiredEventArgs<T>> WidgetRequired;
public void DoSomethingThatNeedsAWidget()
{
Widget widget = OnWidgetRequired();
//...now do something with the widget
}
private T OnWidgetRequired()
{
....
}
}
Upvotes: 2