c#-Windows窗体中通用事件处理程序的解决方法
作者:互联网
相当早以前,我注意到Visual Studio的Windows窗体编辑器不支持包含通用类型参数的事件.例如,类似
public event EventHandler<ListEventArgs<int>> MyStrangeEvent { add { ... } remove { ... } }
哪里
public class ListEventArgs<T> : EventArgs { List<T> args; }
甚至不会显示在Visual Studio属性管理器的事件列表中.现在,这是一个有点人为的示例,可以通过重写类及其事件轻松地对其进行修改以在Visual Studio中工作.但是,我目前正在一个项目中,出于兼容性原因,我不能更改某些类.我唯一能做的就是更改用户控件的事件.当前,此控件的事件如下所示:
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
请注意,不能更改基础的Plane类(由_Plane实例表示,它是一个受保护的字段).在Plane类中声明其DrawingError事件及其EventArgs类型,如下所示:
public class Plane<T> where T : ISurface
{
...
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
...
public class DrawingErrorEventArgs : EventArgs { ... /* Uses T */ ... }
}
当然,Visual Studio的Windows窗体编辑器不会显示我的用户控件的任何事件.我一直在寻找多种解决方法来再次显示它们,但一直无法找到一种有效的解决方法.这是我尝试过的一些方法:
>创建一个从Plane继承的MyPlane类,并改用它:公共事件EventHandler< MyPlane.DrawingErrorEventArgs> DrawingError ….由于我不知道的原因,事件仍然没有显示在编辑器中.也许这是由于事件的参数所致,其中某些参数仍然是通用的.在下面找到一个最小的工作示例.
>创建了一个帮助程序类,该类定义了EventHandler< Plane< GDISurface> .DrawingErrorEventArgs>之间的隐式转换运算符.和EventHandler< GDIPlane.DrawingErrorEventArgs>其中GDIPlane只是从Plane< GDISurface>继承的伪类.这样做确实可以工作,但是由于转换会创建新的事件处理程序,因此会重复事件调用,这些事件处理程序将传递给_Plane,无法正确删除/取消注册.
>试图继承自EventHandler< Plane< GDISurface> .DrawingErrorEventArgs> ;,因为EventHandler< T>被密封.
还有其他方法可以使我的事件在Windows窗体编辑器中再次显示吗?
最好的祝福
安德烈亚斯
编辑:1的最小工作示例:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
public class GDIPlane : Plane<GDISurface> { }
GDIPlane _Plane = null;
public event EventHandler<GDIPlane.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
单击TestControl实例时,DrawingError不会显示在属性管理器的事件列表中.
EDIT2:这是原始问题(没有任何解决方法),其中TestControl的DrawingError事件没有显示:
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError = null;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
Plane<GDISurface> _Plane = null;
public event EventHandler<Plane<GDISurface>.DrawingErrorEventArgs> DrawingError { add { _Plane.DrawingError += value; } remove { _Plane.DrawingError -= value; } }
}
解决方法:
这是特定于Visual Studio的行为,其原因根源在于EventHandler<>.没有在其“ TEventArgs”上指定协方差(这似乎施加了愚蠢的限制),并且这些工具对代码执行的自省不足,无法容纳适当的类型(即使在构造控件时留下了类型数据的痕迹) .)因此,似乎VS不支持通用事件属性.您可能考虑在Microsoft Connect上提交功能请求,我不建议将其作为错误提交,因为他们可能会“按设计”将其标记并关闭.
通常,如果事件上需要通用类型参数,并且需要对它们的设计时支持(这是不同的实现关注点),则可以将它们包装在特定于表示的外观中(例如,“额外的代码层”促进设计时需求”.)
就个人而言,我会减少您现在使用的泛型类型,这似乎有点过分,如果您不了解泛型类型的协方差/相反,可能会在某些时候使您陷入困境,例如现在.
但是,要变通解决此问题:
考虑使用自定义事件args类,该类可以以非通用属性传输数据,也可以使用非通用EventHandler事件/属性.然后,从通用类型参数中移出对事件“类型”的理解,并由非通用事件args负责.如果事件args的“类”不足,则可以添加一个属性来传达事件类型(或数据类型),以便接收代码可以正确地解释它(当然,假设其他人还不知道它)手段.):
public class DataEventArgs : EventArgs
{
//public string EventTypeOrPurpose { get; set; }
public object Data { get; set; }
}
这通常仅用于通过事件链传递数据,通常按以下方式实现:
public class DataEventArgs<T> : EventArgs
{
public T Data { get; set; }
}
不幸的是,这也有一个协方差问题,要解决这个问题,您实际上需要更多类似这样的东西:
public interface IDataArgs<out T>
{
T Data { get; }
}
public class DataEventArgs<T> : EventArgs, IDataArgs<T>
{
public DataEventArgs<T>(T data)
{
_data = data;
}
private T _data;
public T Data { get { return _data; } }
}
即便如此,这些通用版本仍无法解决Visual Studio的限制,这只是您已经向我们展示的内容的更合适的替代形式.
更新:按照要求,从最基本的意义上讲,这是“专用门面”的外观.请注意,在这种情况下,用户控件用作外观层,因为事件处理程序将用户委托公开给基础对象模型.从用户控件(从消费者/设计人员的角度来看)无法直接访问基础对象模型.
请注意,除非您在应用程序的整个生命周期内都弃用了这些用户控件,否则不需要对事件处理程序进行引用跟踪(这样做仅是为了确保根据提供的委托正确地删除委托,该委托包装在闭包/委托中,因为您会在下面看到).
同样值得注意的是,除了验证设计器放到表单上时设计器在属性网格中是否显示DrawingError之外,我没有对该代码进行测试运行.
namespace SampleCase3
{
public interface ISurface { }
public class GDISurface : ISurface { }
public class Plane<T> where T : ISurface
{
public event EventHandler<DrawingErrorEventArgs> DrawingError;
public class DrawingErrorEventArgs : EventArgs { T stuff; }
}
public class TestControl : UserControl
{
private Plane<GDISurface> _Plane = new Plane<GDISurface>(); // requires initialization for my own testing
public TestControl()
{
}
// i am adding this map *only* so that the removal of an event handler can be done properly
private Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>> _cleanupMap = new Dictionary<EventHandler, EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>>();
public event EventHandler DrawingError
{
add
{
var nonGenericHandler = value;
var genericHandler = (EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>)delegate(object sender, Plane<GDISurface>.DrawingErrorEventArgs e)
{
nonGenericHandler(sender, e);
};
_Plane.DrawingError += genericHandler;
_cleanupMap[nonGenericHandler] = genericHandler;
}
remove
{
var nonGenericHandler = value;
var genericHandler = default(EventHandler<Plane<GDISurface>.DrawingErrorEventArgs>);
if (_cleanupMap.TryGetValue(nonGenericHandler, out genericHandler))
{
_Plane.DrawingError -= genericHandler;
_cleanupMap.Remove(nonGenericHandler);
}
}
}
}
}
为了补充上述内容,这是非通用事件处理程序现在的样子:
private void testControl1_DrawingError(object sender, EventArgs e)
{
var genericDrawingErrorEventArgs = e as Plane<GDISurface>.DrawingErrorEventArgs;
if (genericDrawingErrorEventArgs != null)
{
// TODO:
}
}
请注意,这里的使用者必须具有类型的知识才能执行转换.在转换应该成功的假设下,使用as运算符将绕过祖先检查.
像这样的事情与您将要获得的接近.是的,按照我们的大多数标准来看,这是很丑陋的,但是,如果您绝对“需要”这些组件之上的设计时支持,并且您无法更改Plane< T> (可能会更合适),那么这是唯一可行的解决方法.
高温超导
标签:visual-studio-2013,event-handling,generics,c,winforms 来源: https://codeday.me/bug/20191120/2046138.html