我正在try 创建一个自定义事件,其事件参数包含一个通用参数.我甚至不确定这是否可以做到,或者我只是做得不对.

这里有一个非常简单的例子.它是一个对象类,当其属性之一即将更改时将引发事件.事件参数应包含一个属性,该属性是更改属性的当前值,以便用户可以根据需要取消操作.我知道你可以简单地看一下引发事件的对象,但请忽略这一点,更多地关注这个问题的机制.

此外,我知道这个示例不会编译,但它展示了我正在努力实现的目标.

首先,事件args类:

public class FooPropertyChangingEventArgs<T> : System.EventArgs
{
    public FooPropertyChangingEventArgs(string propertyName, T existingValue)
    {
        this.PropertyName = propertyName;
        this.ExistingValue = existingValue;
    }
    
    public bool Cancel { get; set; }
    public string PropertyName { get; }
    public T ExistingValue { get; } // I know that ExistingValue could just be an object type, but I am more interested in finding a way to make it a generic type.
}

代表.这是问题的一部分,因为如果我将类型参数添加到事件参数,我还需要将其添加到事件处理程序:

public delegate void FooPropertyChangingEventHandler<T>(object sender, FooPropertyChangingEventArgs<T> e);

这是我的简单对象类.具有不同数据类型的两个属性:

public class Foo
{
    // First problem.  Because I added the type argument to the event handler, I need to add it here.  However, I can't because at 
    //  this level I have no way of knowing which property will be changed.  In fact, the event has to work for all of the properties.
    public event FooPropertyChangingEventHandler OnFooPropertyChanging;
    
    private int _myInt = 0; 
    private string _myString = String.Empty;
    
    public Foo(int myInt, string myString)
    {
        // Prevent event calls upon object initialization.
        this._myInt = myInt;
        this._myString = myString;
    }
    
    public int MyInt 
    { 
        get { return this._myInt; }
        set
        {
            // Here I want to pass an int to any listener.
            FooPropertyChangingEventArgs<int> args = new FooPropertyChangingEventArgs<int>("MyInt", this._myInt);
            this.OnFooPropertyChanging?.Invoke(this, args);
            if(!args.Cancel)
            {
                this._myInt = value;
            }
        }
    }
    
    public string MyString 
    { 
        get { return this._myString; }
        set 
        {
            // Here I want to pass a string to any listener.
            FooPropertyChangingEventArgs<string> args = new FooPropertyChangingEventArgs<string>("MyString", this._myString);
            this.OnFooPropertyChanging?.Invoke(this, args);
            if(!args.Cancel)
            {
                this._myString = value;             
            }
        }
    }
}

最后,订阅者:

public class FooSubscriber
{
    private Foo _foo;
    private int _runningTotal = 0;
    private string _runningString = String.Empty;
    
    public FooSubscriber()
    {
        this._foo = new Foo();
        this._foo.FooPropertyChanging += this._foo_PropertyChangingEventArgs;
    }
    
    // This is what should be called by a consumer to change the properties and raise the events.
    public void ChangeFoo(int newMyInt, string newMyString)
    {
        this._foo.MyInt = newMyInt;
        this._foo.MyString = newMyString;
    }
    
    // This is also part of the problem.  The compiler expects the event args to have a type argument (e.g., FooPropertyChangingEventArgs<T>), and I understand 
    // that at this level there is no way to know which property is being raised.  Or is there a way I am missing?
    private void _foo_PropertyChangingEventArgs(object sender, FooPropertyChangingEventArgs e)
    {
        // No changes allowed in June. Stupid, but this demonstrates how we want to cancel if "something" happens.  
        if(DateTime.Now.Month == 6)
        {
            e.Cancel = true;
        }
        else
        {
            // If I could make this work, I would expect to be able to do something like this here:
            switch(e.PropertyName)
            {
                case "MyInt":
                    // I would expect ExistingValue to be an int.
                    this._runningTotal += e.ExistingValue + 1;  
                    break;
                case "MyString":
                    // I would expect ExistingValue to be a string.
                    this._runningString += e.ExistingValue.ToUpper();
                    break;
                default:
                    this._runningString = 0;
                    this._runningString = String.Empty;
                    break;
            }
        }
    }
}

我知道可能有什么问题.也就是说,订阅者不可能知道事件args属性是什么数据类型.对吗?我认为泛型应该通过在运行时确定数据类型来解决这个问题.如何指定希望事件参数具有泛型属性,而不强制事件处理程序执行该行为?

那么,这能做到吗?我错过什么了吗?

提前感谢,

推荐答案

你的根本问题是,你希望有一个单一的方法,可以在某种程度上接受一个论点,这是两个不同的东西在同一时间.

当然,这在保持强类型的同时是不可能的.

行动的过程完全取决于你打算如何实现这些价值观.根据您对他们所做的事情,您可能可以研究策略模式之类的东西,作为处理各种价值观的一种方式.

Csharp相关问答推荐

在ASP.NET中为数据注释 Select 合适的语言

C#将参数传递给具有变化引用的变量

CsWin32如何创建PWSTR的实例,例如GetWindowText

处理. netstandard2.0项目中HttpClient.SslProtocol的PlatformNotSupportedException问题""

. NET WireMock拒绝PostAsJsonAsync序列化

C#.NET依赖项注入顺序澄清

异步实体框架核心查询引发InvalidOperation异常

用于管理System.Text.Json中的多态反序列化的自定义TypeInfoResolver

Swagger没有显示int?可以为空

HttpRequestMessage.SetPolicyExecutionContext不会将上下文传递给策略

C#自定义验证属性未触发IsValid方法

在Windows Plesk上发布ASP.NET Core 7 Web API-错误:无法加载文件或程序集';Microsoft.Data.SqlClient';

在PostgreSQL上使用ExecuteSqlRawAsync的C#11原始字符串文字有区分大小写的问题

我找不到ASP.NET Web应用程序(.NET框架).已 Select .NET框架项目和项模板以及此组件(&Q;)

Blazor:搜索框在第一次搜索时不搜索

如何在使用属性 Select 器时判断是否可以为空

我是否应该注销全局异常处理程序

无法向Unity注册Microsoft Logger

如何读取TagHelper属性的文本值?

无法使用直接URL通过PictureBox.ImageLocation加载图像