问题
I've been working with generics over the last few days and I encountered an issue by trying to pass generic types as parameters in events/delegates.
A very friendly member of stack could bring some light into one question about generics I posted last Friday. However, I'd like to bring this up again, because I still don't understand how I am supposed to make it work correctly.
In the following code you can see that I have a class "Catalog", which can raise an event, which contains a generic parameter.
The class "MainWindow" subscribes to this event and should work with the generic parameter.
This doesn't work, because the event must specify the generic type of the delegate.
public class Catalog {
#region EVENTS
public delegate void GenericListItemCountChangedEvent<T>(object sender, GenericListItemCountChangedEventArgs<T> e) where T : BaseItem;
//This is the point where it does not work, because I specify BaseItem as the type
public event GenericListItemCountChangedEvent<BaseItem> GenericListItemCountChanged;
private void RaiseGenericListItemCountChangedEvent<T>(List<T> List) where T : BaseItem {
if (GenericListItemCountChanged != null) {
GenericListItemCountChanged(this, new GenericListItemCountChangedEventArgs<T>(List));
}
}
public class GenericListItemCountChangedEventArgs<T> : EventArgs where T : BaseItem {
private List<T> _changedList_propStore;
public List<T> ChangedList {
get {
return _changedList_propStore;
}
}
public GenericListItemCountChangedEventArgs(List<T> ChangedList){
_changedList_propStore = ChangedList;
}
}
#endregion EVENTS
}
public partial class MainWindow : Window{
public MainWindow(){
_catalog.GenericListItemCountChanged += (sender, e) => GenericListItemCountChanged(sender, e);
}
private void GenericListItemCountChanged<T>(object sender, Catalog.GenericListItemCountChangedEventArgs<T> e) where T : BaseItem {
//Use Generic EventArgs
}
}
Is there a way to receive the generic parameter in the class "MainWindow"? Or do I need to implement some work-around?
回答1:
Depending on your exact needs, you might be able to preserve the generic aspect of your method. But I admit, it's not clear to me why you would want to do that. The method is tied to a specific delegate type, i.e. the type used by the event that method specifically raises.
Of what value would there be in having it generic at all? Are you really going to raise this one event with various different lists, each having different element types? If so, how is the event handler supposed to know what list the event is being raised for? I mean, it can be done via reflection, but that's hardly good design.
In any case, if you really want to get your code to compile, you will need to impose some restrictions on the classes involved, and use an interface instead of an actual class in the delegate type declaration.
For example:
interface IBaseItemEventArgs<out T>
{
IReadOnlyList<T> ChangedList { get; }
}
class GenericListItemCountChangedEventArgs<T> : EventArgs, IBaseItemEventArgs<T>
where T : BaseItem
{
private IReadOnlyList<T> _changedList_propStore;
public IReadOnlyList<T> ChangedList
{
get
{
return _changedList_propStore;
}
}
public GenericListItemCountChangedEventArgs(List<T> ChangedList)
{
_changedList_propStore = ChangedList.AsReadOnly();
}
}
public delegate void
GenericListItemCountChangedEvent<in T>(object sender, IBaseItemEventArgs<T> e)
where T : BaseItem;
public static event
GenericListItemCountChangedEvent<BaseItem> GenericListItemCountChanged;
private static void RaiseGenericListItemCountChangedEvent<T>(List<T> List)
where T : BaseItem
{
GenericListItemCountChangedEvent<T> handler = GenericListItemCountChanged;
if (handler != null)
{
handler(null, new GenericListItemCountChangedEventArgs<T>(List));
}
}
This declares the interface IBaseItemEventArgs<out T>
, i.e. with T
being covariant (i.e. the actual type returned may be more derived than that used in the declaration that uses the interface), and the event delegate type GenericListItemCountChangedEvent<in T>
, i.e. with T
being contravariant (i.e. the type of the delegate assigned to the method's handler may have arguments less derived than that used in the declaration of that local handler variable).
These two together allow:
- The
RaiseGenericListItemCountChangedEvent<T>()
method to implicitly convert from the event delegate type, which has a declared type that is less-derived than theT
in the method declaration, to the handler type the method actually deals with. - The
IBaseItemEventArgs<out T>
interface to have a method which returns a similarly covariant interface object,IReadOnlyList<T>
.
Of course, if the handler needs to be able to modify the list, then the code won't compile (can't use IReadOnlyList<T>
). But that's a good thing. If a caller was able to subscribe to the event with a list element type more derived than that declared and still be permitted to change the list, it could add elements of the wrong type to the list. That wouldn't be good at all. :) So the language rules prevent you from making that mistake.
回答2:
ou can make Catalog class generic. That will allow you to use GenericListItemCountChangedEvent T
public class Catalog<T> where T: BaseItem
{
public delegate void GenericListItemCountChangedEvent<T>(object sender, GenericListItemCountChangedEventArgs<T> e) where T : BaseItem;
//This is the point where it does not work, because I specify BaseItem as the type
public event EventHandler<GenericListItemCountChangedEventArgs<T>> GenericListItemCountChanged;
private void RaiseGenericListItemCountChangedEvent(List<T> List)
{
if (GenericListItemCountChanged != null)
{
GenericListItemCountChanged(this, new GenericListItemCountChangedEventArgs<T>(List));
}
}
public class GenericListItemCountChangedEventArgs<T> : EventArgs where T : BaseItem
{
private List<T> _changedList_propStore;
public List<T> ChangedList
{
get
{
return _changedList_propStore;
}
}
public GenericListItemCountChangedEventArgs(List<T> ChangedList)
{
_changedList_propStore = ChangedList;
}
}
}
public class MainWindow
{
public MainWindow()
{
new Catalog<BaseItem>().GenericListItemCountChanged += (sender, e) => GenericListItemCountChanged(sender, e);
}
private void GenericListItemCountChanged<T>(object sender, Catalog<BaseItem>.GenericListItemCountChangedEventArgs<T> e) where T : BaseItem
{
//Use Generic EventArgs
}
}
来源:https://stackoverflow.com/questions/27904362/c-sharp-passing-generic-class-as-an-event-parameter