I am trying to create a custom event whose event args contains a generic parameter. I am not sure this can even be done, or if I am just doing it incorrectly.
Here is a very simple example. It is an object class that will raise an event when one of its properties is about to change. The event args should contain a property that is the current value of the changing property so that the user may cancel the action, if desired. I understand that you could simply look at the object that raised the event, but please ignore that and focus more on the mechanics of this question.
Also, I know that this example won't compile, but it demonstrates what I am trying to achieve.
First, the event args class:
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.
}
The delegate. This is part of the problem because if I add the type argument to the event args, I need to also add it to the event handler:
public delegate void FooPropertyChangingEventHandler<T>(object sender, FooPropertyChangingEventArgs<T> e);
This is my simple object class. Two properties with different data types:
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;
}
}
}
}
And, finally, the subscriber:
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;
}
}
}
}
I kind of get what may be wrong. That is, the subscriber can't possibly know what data type the event args property is. Is that correct? I thought that generics were supposed to solve that by determining the data type at runtime. And how can I specify that I want the event args to have a generic property without forcing that behavior on the event handler?
So, can this be done? Am I missing something?
Thanks in advance, Scott