How to pass custom parameter to event handler - c#

I have the following code, which based on a condition assigns or unassigns a parameterized event handler to some objects, using an anonymous (lambda) method:
{
if (condition)
foreach (var channel in dataSource.Channels)
{
channel.NewSamples += (s, vals) => AddSamples(channel.Index, vals);
}
}
else
{
foreach (var channel in dataSource.Channels)
{
channel.NewSamples -= (s, vals) => AddSamples(channel.Index, vals);
}
}
}
private void AddSamples(int channelIndex, IEnumerable<int> samples)
{
/// do work
}
I suspect this wouldn't unsubscribe correctly, and so I would like to pass channel.Index as parameter to a named handler, but I don't know how to do it.

You can store your handlers in a dictionary, by channel, like this:
Dictionary<Channel, YourEventHandler> _handlers = new Dictionary<Channel, YourEventHandler>();
...
if (condition)
foreach (var channel in dataSource.Channels)
{
if (!_handlers.ContainsKey(channel)) {
YourEventHandler handler = (s, vals) => AddSamples(channel.Index, vals);
channel.NewSamples += handler;
_handlers[channel] = handler;
}
}
}
else
{
foreach (var channel in dataSource.Channels)
{
if (_handlers.ContainsKey(channel)) {
channel.NewSamples -= _handlers[channel];
_handlers.Remove(channel);
}
}
}

Related

Creating a method to subscribe to an event but fire only once

I'm trying to write a method that attaches adds itself to an event, and then removes itself after it was invoked.
Those are my failed attempts:
https://imgur.com/a/PvrPUYY
public Action myEvent = () => {};
void Bar(){
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () =>
{
Debug.Log("Attempt 0");
myEvent -= invokeThenUnsubscribe;
};
myEvent += invokeThenUnsubscribe;
SubscribeOnce(myEvent, () => Debug.Log("Attempt 1"));
myEvent.SubscribeOnce(() => Debug.Log("Attempt 2"));
myEvent.Invoke();
myEvent.Invoke();
}
public Action SubscribeOnce( Action a, Action actionToSubscribe ) {
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () =>
{
actionToSubscribe.Invoke();
a -= invokeThenUnsubscribe;
};
a += invokeThenUnsubscribe;
return a;
}
public static class ActionsEx {
public static void SubscribeOnce(this Action a, Action actionToSubscribe){
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () => {
actionToSubscribe.Invoke();
a -= invokeThenUnsubscribe;
};
a += invokeThenUnsubscribe;
}
}
I realize it's happening because, after using +=, I lose the reference to the new event.
Does anyone have any idea how I can achieve the effect I'm looking for?
Thanks in advance
When you call SubscribeOnce, you aren't passing a reference to "the event" (technically a field, but: semantics); instead, you're reading the current value of the event (field), and passing that to the method. At that point, it the parameter value is completely divorced from the event (field), and no change to a (now a captured parameter) will have any effect on the original event (field). You're only "unsubscribing" a local delegate that has nothing to do with the event (field).
To do what you'd want, you'd need to pass the originating object in as the parameter, i.e. something like:
public static void SubscribeOnce(this SomeType obj, Action actionToSubscribe){
Action invokeThenUnsubscribe = null;
invokeThenUnsubscribe = () => {
actionToSubscribe.Invoke();
obj.TheEvent -= invokeThenUnsubscribe;
};
obj.TheEvent += invokeThenUnsubscribe;
}
}
where SomeType (which could be an interface) defines an event Action TheEvent;, with usage this.SubscribeOnce(() => Debug.Log("Attempt 2")); (or whatever object you want to use)
I am not crazy about the fact that you are mixing actions with events. Instead, I would use a more object oriented approach and separate the two concepts by creating an interface to represent the subscribable objects that expose some event and then use an extension method to allow all actions to subscribe once to these objects:
//Delegate to handle events
public delegate void SomeEventEventHandler(ISubscribable sender, EventArgs args);
//Interface that describes an object you can subscribe to
public interface ISubscribable
{
event SomeEventEventHandler MyEvent;
}
//Implementation of a subscribable object (base class)
public class SomeObject : ISubscribable
{
private SomeEventEventHandler _handler;
public event SomeEventEventHandler MyEvent
{
add { _handler += value; }
remove { _handler -= value; }
}
public void RaiseEvent()
{
_handler?.Invoke(this, new EventArgs());
}
}
//Extension method for actions
public static class ActionExtensions
{
public static void SubscribeOnce(this Action action, ISubscribable subscribable)
{
SomeEventEventHandler handler = null;
handler = new SomeEventEventHandler((ISubscribable subscribable, EventArgs args) =>
{
action();
subscribable.MyEvent -= handler;
});
subscribable.MyEvent += handler;
}
}
//Usage:
var obj = new SomeObject();
obj.MyEvent += (ISubscribable sender, EventArgs args) =>
{
Console.WriteLine("Attempt 0 (always fires).");
};
Action invokeThenUnsubscribe = () => { Console.WriteLine("Attempt 1"); };
invokeThenUnsubscribe.SubscribeOnce(obj);
Action invokeThenUnsubscribe2 = () => { Console.WriteLine("Attempt 2"); };
invokeThenUnsubscribe2.SubscribeOnce(obj);
obj.RaiseEvent();
obj.RaiseEvent();
obj.RaiseEvent();
Output:
Attempt 0 (always fires).
Attempt 1
Attempt 2
Attempt 0 (always fires).
Attempt 0 (always fires).
Alternatively, you can skip the action extension all together and provide this functionality as a method of the class:
//In class SomeObject
public void SubscribeOnce(Action action)
{
SomeEventEventHandler handler = null;
handler = new SomeEventEventHandler((ISubscribable subscribable, EventArgs args) =>
{
action();
subscribable.MyEvent -= handler;
});
this.MyEvent += handler;
}
//Usage:
var obj2 = new SomeObject();
obj2.MyEvent += (ISubscribable sender, EventArgs args) =>
{
Console.WriteLine("Attempt 0 (always fires).");
};
obj2.SubscribeOnce(() => { Console.WriteLine("Attempt 1"); });
obj2.SubscribeOnce(() => { Console.WriteLine("Attempt 2"); });
obj2.RaiseEvent();
obj2.RaiseEvent();
obj2.RaiseEvent();
This produces the same output.

How to declare events inside a dictionary initialization?

I am creating a Dictionary of events and I want to declare those events inside the initialization of that dictionary instead of declaring them somewhere else and placing the links to the dictionary.
static event EventDelegate Event1;
static event EventDelegate Event2;
static event EventDelegate Event3;
public enum EventTypes
{
Event1,
Event2,
Event3,
}
public static Dictionary<EventTypes, EventDelegate> events = new Dictionary<EventTypes, EventDelegate>
{
{EventTypes.Event1, Event1},
{EventTypes.Event2, Event2},
{EventTypes.Event3, Event3},
};
So I want to do something like that:
{EventTypes.Event1, new event EventDelegate Event1}
Is that possible?
How about wrapping the events?
class MyEventWrapper
{
public event EventDelegate Handlers;
public void Raise(object sender, EventArgs args)
{
Handlers?.Invoke(sender, args);
}
}
//
Dictionary<EventTypes, MyEventWrapper> eventMap = new Dictionary<EventTypes, MyEventWrapper>
{
{ EventTypes.Event1, new MyEventWrapper() },
{ EventTypes.Event2, new MyEventWrapper() },
};
//
eventMap[EventTypes.Event1].Handlers += (s, a) => { };
eventMap[EventTypes.Event2].Handlers += (s, a) => { };
//
eventMap[EventTypes.Event1].Raise(this, new EventArgs());
If you define your dictionary like this:
public delegate void EventDelegate(object data);
public static Dictionary<EventTypes, EventDelegate> Events =
new Dictionary<EventTypes, EventDelegate>
{
{ EventTypes.Event1, (EventDelegate)((_) => { }) },
{ EventTypes.Event2, (EventDelegate)((_) => { }) },
{ EventTypes.Event3, (EventDelegate)((_) => { }) },
};
public enum EventTypes
{
Event1,
Event2,
Event3,
}
Then this code works a treat:
Events[EventTypes.Event1] += (object data) => Console.WriteLine($"Event1 (1): {data}");
Events[EventTypes.Event1] += (object data) => Console.WriteLine($"Event1 (2): {data}");
Events[EventTypes.Event2] += (object data) => Console.WriteLine($"Event2: {data}");
Events[EventTypes.Event1]("A");
Events[EventTypes.Event2]("B");
Events[EventTypes.Event3]("C");
The output I get is:
Event1 (1): A
Event1 (2): A
Event2: B
You are then clearly declaring those events inside the initialization of the dictionary.

Invalid expression term 'string' error in event and delegates

Using event and delegates I have tried this.
public delegate void ColourChangerAction(string str);
public static event ColourChangerAction OnClicked;
void OnGUI() {
if(GUI.Button(new Rect(Screen.width/2, Screen.height-200, 50,50),texture1)){
if (OnClicked != null) {
OnClicked(string strr);///error here!!!!!!!!!!!!!!!!
}
}
but it is showing me the error Invalide expression term string. If I don't provide string pararmenter then it shows does not take 0 arugements.
well I'm new to delegates and events. what I'm missing.??
while in another class I am doing this for getting event call.
EventManager.OnClicked += ColourChanger;//registering
public void ColourChanger(string colourName)
{
print("Colour Changer " + colourName);
if (colourName == "Red")
{
print(gameObject.name);
hitInfo.collider.gameObject.renderer.material.color = Color.red;
}
else if (colourName == "Green")
{
print(gameObject.name);
hitInfo.collider.gameObject.renderer.material.color = Color.green;
}
else if (colourName == "Yellow")
{
print(gameObject.name);
hitInfo.collider.gameObject.renderer.material.color = Color.yellow;
}
}
You have to register that event
OnClickedE += new ColourChangerAction(OnGUI);
You have to re-define the methode signature for subscribe this event.
void OnGUI(string str)
{
if (1 == 1)
{
if (OnClickedE != null)
{
// OnClicked(string strr);///error here!!!!!!!!!!!!!!!!
}
}
}
Hence your Whole code will be like the following:
//Defining Delegate and event
public delegate void ColourChangerAction(string str);
public static event ColourChangerAction OnClicked;
// Method signature for subscribe the event
static void OnGUI(string str)
{
if (1 == 1)
{
if (OnClickedE != null)
{
// perform your action here
}
}
}
//register the event
OnClicked += new ColourChangerAction(OnGUI);
// Invoke the event dynamically
OnClicked.DynamicInvoke("Some string");
Update:
In your case please replace the code OnClicked(string strr); with
OnClicked.DynamicInvoke("red");// pass the color here
1) Do not use static events.
Negative Aspects/Bad Practice of Static Event in C#
2) Use Event Design
Do use System.EventHandler instead of manually creating new delegates to be used as event handlers.
Example:
static void Main(string[] args)
{
Counter c = new Counter( ... );
c.ThresholdReached += c_ThresholdReached;
...
}
static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)
{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
...
}
class Counter
{
...
public void Add(int x)
{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);
}
}
protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)
{
//Thread safe event
EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
}
public class ThresholdReachedEventArgs : EventArgs
{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}

Reactive extensions dispose event handler

Hi guys I have a ExternalDataService that is constantly firing real time data, it is contained in a lib.
I have a wrapper class that subscribes and puts the updates on a Observable..
public class MyService
{
private ExternalDataService _externalDataService;
public MyService()
{
_externalDataService= new ExternalDataService ();
}
public IObservable<double> GetData()
{
return Observable.Create<double>(i =>
{
_externalPriceService.OnDataChanged += (s, e) => { i.OnNext(e); };
return () =>
{
// what do I do here?
};
});
}
}
Consumed as...
var p = new MyService();
var disposable = p.GetData().Subscribe(i => Console.WriteLine(i));
How would I unsubscribe from _externalPriceService.OnDataChanged when the Dispose is called on disposable?
Use Observable.FromEvent or Observable.FromEventPattern, instead of Observable.Create. Then you just Dispose the subscription.
Depending on your EventHandler definition, it would be something like this:
public class ExternalDataService {
public EventHandler<DataChangedEventArgs> OnDataChanged;
}
public class DataChangedEventArgs : EventArgs {
public Double Data {
get; set;
}
}
public class MyService {
private ExternalDataService _externalDataService;
public MyService()
{
_externalDataService= new ExternalDataService ();
}
public IObservable<double> GetData()
{
return Observable.FromEventPattern<DataChangedEventArgs>(eh => _externalDataService.OnDataChanged += eh, eh => _externalDataService.OnDataChanged -= eh)
.Select(e => e.EventArgs.Data);
}
}
You can also do something like this:
public IObservable<double> GetData()
{
Action<object, double> dataHandler = null;
return Observable.Create<double>(i =>
{
dataHandler = (s, e) => { i.OnNext(e); };;
_externalDataService.OnDataChanged += dataHandler;
return Disposable.Create(() =>
{
_externalDataService.OnDataChanged -= dataHandler;
});
});
}
edit: stupid typos

Adding an action to event handler arguments c#

I have a problem with some code I need to refactor. Right now it uses lambdas as event handlers, but they are not removed properly. From what I have read, this is not even possible? Anyway I would like to rewrite it to use a delegate instead of an anonymous function, and now my problem is that right now it takes an action as parameter, and I can't seem to figure out how to pass the action on to my new delegate. This is the code:
void RetrieveData(
int pointId,
int? chartCollectionId,
Action action)
{
if (pointId <= 0)
throw new ArgumentException("PointId not valid");
LastPointId = NextPointId;
NextPointId = pointId;
Clear();
_csr = new CustomerServiceRepository();
_csr.ServiceClient.GetChartDataCompleted += (se, ea) =>
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr = null;
};
_csr.ServiceClient.GetChartDataAsync(
Settings.Current.Customer.CustomerName,
pointId,
chartCollectionId);
_csr.ServiceClient.GetChartDataCompleted -= (se, ea) => //remove after usage
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr = null;
};
}
I was thinking that maybe I could create the following:
public class extendedEventArgs : GetChartDataCompletedEventArgs
{
Action foo { get; set; }
}
void tang(object sender, extendedEventArgs e)
{
_cachedCharts = e.Result;
ChartDataRetrieved(e.Result);
if (action != null)
action.Invoke();
_csr = null;
}
And the pass the action as a parameter in the extended event args, but when I try to use it like this
_csr.ServiceClient.GetChartDataCompleted += new EventHandler<extendedEventHandler>(tang);
It gives an error:
Cannot implicitly convert type System.EventHandler<Conwx.Net.Client.CustomerClient.Controls.ChartControls.ChartListForecast.extendedEventArgs>' to System.EventHandler<Conwx.Net.Client.Framework.CustomerServiceReference.GetChartDataCompletedEventArgs>'
What am I doing wrong here? Alternative solutions are also welcome.
.K
As I read it, the key problem here is not being able to remove the handler; if so, all you need it to store the delegate (where in the below, YourDelegateType is meant to mean: the defined type of GetChartDataCompleted):
YourDelegateType handler = (se, ea) =>
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr = null;
};
_csr.ServiceClient.GetChartDataCompleted += handler;
...
_csr.ServiceClient.GetChartDataCompleted -= handler;
You can also make it self-unsubscribing (i.e. so that it unsubscribes when the event is raised):
YourDelegateType handler = null;
handler = (se, ea) =>
{
_cachedCharts = ea.Result;
ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
_csr.ServiceClient.GetChartDataCompleted -= handler;
_csr = null;
};
_csr.ServiceClient.GetChartDataCompleted += handler;
No, you can't do this because it's the class which raises the GetChartDataCompleted event which creates the object passed (as a reference) to the event handler. It will be creating a GetChartDataCompletedEventArgs - not an extendedEventArgs.
If you think about it, it's like trying to implement an interface which looks like this:
public interface IFoo
{
void Foo(object x);
}
with a class like this:
public class Bar : IFoo
{
// We don't care if someone calling IFoo wants to pass us something
// other than a string - we want a string, darn it!
public void Foo(string y)
{
Console.WriteLine(y.Length);
}
}
That's clearly not going to work...
Marc has shown one approach to fixing it - but I'd also point out that you should probably actually only be removing the delegate when the event fires. I'm assuming that the fact that the method is called GetChartDataAsync means it's a non-blocking method... so unsubscribing from the event immediately after calling it probably isn't a great idea.
If you'd prefer to avoid the anonymous methods, you can manually do essentially what the compiler is doing for you under the hood. That is, create a closure class to hold the Action and a reference to itself as fields and which exposes the method you want to assign to the event. Something like this:
class RetrieveDataClosure
{
private Action action;
private MyClass self;
public RetrieveDataClosure(Action action, MyClass self)
{
this.action = action;
this.self = self;
}
public void ChartDataCompleted(object se, MyEventArgs ea)
{
self._cachedCharts = ea.Result;
self.ChartDataRetrieved(ea.Result);
if (action != null)
action.Invoke();
self._csr = null;
}
}
Which you'd use in your code like this:
var closure = new RetrieveDataClosure(action, this);
_csr = new CustomerServiceRepository();
_csr.ServiceClient.GetChartDataCompleted += closure.ChartDataCompleted;
_csr.ServiceClient.GetChartDataAsync(
Settings.Current.Customer.CustomerName,
pointId,
chartCollectionId);
_csr.ServiceClient.GetChartDataCompleted -= closure.ChartDataCompleted;

Categories

Resources