Action with pre prepered parameters - c#

I want to register some actions / delegates / Events or something like this with pre prepered parameters (no parameters on invoke).
E.g.
public Action actions;
public void Main()
{
actions += Add(12);
actions += Show("something");
actions += Calculate(2, 4);
actions?.Invoke();
}
public void Add(int k) { }
public void Show(string s) { }
public void Calculate(int k, int c) { }
Alternatively something like this:
public void Main()
{
actions += Add(12);
actions += Add(1);
actions += Add(4);
actions?.Invoke();
}
Is it possible to do that?

Related

Can I use a action to pass a value to a method parameter?

I will try my best to explain what I am asking.
Imagine I have a Action in one class:
public Action Something;
And in another class I subscribe a method ot it with a parameter
private void Foo(int num)
{
Debug.Log(num);
}
OneClass.Something += Foo;
So basically I want the parameter of Foo to be something that the OneClass Passes. Is that something that exists in C#?
Is this what you are trying to do:
public class Bar
{
public event Action<int> Something;
protected void OnSomething(int arg)
{
Something?.Invoke(arg);
}
}
class Program
{
static void Main(string[] args)
{
var bar = new Bar();
bar.Something += Foo;
}
static void Foo(int x)
{
Debug.WriteLine(x);
}
}
What I have above is an event .Something defined with an event handler of Action<int> function prototype. Action<T> represents methods of the form void f(T item).
Then I subscribe to the event with bar.Something += Foo
Finally, I define the method OnSomething() in order to allow the class Bar to trigger the event when needed.

How do I create a public action, but keep the invoke private?

Let say I have an action like this:
public Action OnSomeAction;
I would like to be able to subscribe to this action from outside of the class, but not be able to invoke it:
OnSomeAction.Invoke();
Is there a way to do this without making the action private and creating methods for subscribing and unsubscribing like this:
private Action _someAction;
public void Subscribe(Action listener)
{
_someAction += listener;
}
public void Unsubscribe(Action listener)
{
_someAction -= listener;
}
private void Invoke()
{
_someAction.Invoke();
}
Are you looking for event?
public class MyClass {
// Let firing event be private
private void onMyAction() {
Action action = MyAction;
if (action != null)
action();
}
public void FireDemo() {
onMyAction();
}
//TODO: I've put Action, but, probably, EventHandler will be a better choice
// while event itself (subscribe / unsubscribe) being public
public event Action MyAction;
}
Demo:
MyClass myClass = new MyClass();
var first = () => {Console.WriteLine("I'm the first")};
var second = () => {Console.WriteLine("I'm the second")};
var none = () => {Console.WriteLine("I should not fire")};
myClass.MyAction += first;
myClass.MyAction += second;
myClass.MyAction += none;
// Unsubsribe
myClass.MyAction -= none;
// Direct attempt will NOT compile:
// myClass.MyAction();
myClass.FireDemo();
Although this looks a lot like events. You can achieve it using Action too. Consider following code which is almost same as you suggested:
private List<Action> _someActionList = new List<Action>();
public void Subscribe(Action listener)
{
_someActionList.Add(listener);
}
public void Unsubscribe(Action listener)
{
_someActionList.Remove(listener);
}
private void Invoke()
{
foreach(action in _someActionList)
{
action();
}
}
I hope this is exactly what you want to do. If not then please elaborate further.

Send static method + parameter as parameter

I'd like to do something like this, but it's not possible.(Cann't convert from 'void' to 'System.Action').
class Program
{
public static void Main()
{
int n = 2;
ClassB cb = new ClassB();
cb.SetMethod(ClassA.MethodA(n)); //Cann't convert 'void' to 'System.Action<int>'
}
}
public class ClassA
{
public static void MethodA(int a)
{
//code
}
}
public class ClassB
{
Delegate del;
public void SetMethod(Action<int> action)
{
del = new Delegate(action);
}
public void ButtonClick()
{
del.Invoke();
}
}
public delegate void Delegate(int n);
I can send the argument "n", as second argument in the "setMethod" method, but I would have to store a variable to after pass to "del.Invoke(PARAM)". I'd like to use "del.Invoke()".
You seem to have a misunderstanding of delegates. Delegates represent methods, not method calls. If you supply arguments to a method, it becomes a method call. So here:
cb.setMethod(ClassA.methodA(n));
ClassA.methodA(n) is a method call, and you can't assign that to a delegate.
Basically, you can't pass the parameter at this stage. You have to pass the parameter when you invoke the delegate. e.g.
del.Invoke(5);
But you said you want to always write del.Invoke(), with no arguments. Well, then you should not use an Action<int>, you should just use Action, which does not accept any parameters.
class Program
{
public static void Main()
{
int n = 2;
ClassB cb = new ClassB();
cb.setMethod(() => ClassA.methodA(n));
}
}
public class ClassA
{
public static void methodA(int a)
{
//code
}
}
public class ClassB
{
Delegate del;
public void setMethod(Action action)
{
del = new Delegate(action);
}
public void ButtonClick()
{
del.Invoke();
}
}
public delegate void Delegate();
cb.setMethod(new Action(ClassA.methodA));
It isn't clear whether you want to capture the integer at the call site (e.g. as a closure), or whether you intend passing in a parameter explicitly to the delegate.
Here's the former case, where the value is captured:
public static void Main()
{
var n = 2;
var cb = new ClassB();
cb.setMethod(() => ClassA.methodA(n));
}
The delegate is thus unaware of the captured variable, and is just defined as:
public delegate void Delegate();
If however you do intend passing the int at invoke time, then the value for the int needs to be passed in the ButtonClick:
public static void Main()
{
var cb = new ClassB();
cb.setMethod(ClassA.methodA);
}
public class ClassB
{
Delegate del;
public void setMethod(Action<int> action)
{
del = new Delegate(action);
}
public void ButtonClick()
{
var n = 2;
del.Invoke(n);
}
}
public delegate void Delegate(int n);
Edit - Re Do you think there's a better way
There's no real reason to explicitly require a delegate. Action and Func (and Action<int>, depending on the above) are already delegates. As an improvement, you should check that the action is assigned before invoking it. The null conditional operator will simplify this as _action?.Invoke(). But you can go one step further, and prevent the action from ever being unassigned by requiring it in the constructor:
public class ClassB
{
// Can be readonly if it is assigned only ever once, in the ctor.
private readonly Action _action;
public ClassB(Action action)
{
Contract.Assert(action != null);
_action = action;
}
public void ButtonClick()
{
_action(); // i.e. no need for Invoke or null check.
}
}

Why is a "forwarded" event not raised when assigning a method group but is when assigning a delegate?

Given the following code:
public delegate void Signal();
public static class SignalExtensions
{
public static void SafeInvoke(this Signal signal)
{
Signal copy = signal;
if (copy != null)
{
copy();
}
}
}
public class RootEventSource
{
public event Signal RootEvent;
public void Raise()
{
this.RootEvent.SafeInvoke();
}
}
public class EventForwarder
{
private readonly RootEventSource rootEventSource;
public EventForwarder(RootEventSource rootEventSource)
{
this.rootEventSource = rootEventSource;
// this is the critical part
this.rootEventSource.RootEvent
+= () => this.AnotherEvent.SafeInvoke();
}
public event Signal AnotherEvent;
// just an example of another method which is using the root event source
public override string ToString()
{
return this.rootEventSource.ToString();
}
}
class Program
{
static void Main(string[] args)
{
var rootEventSource = new RootEventSource();
var eventForwarder = new EventForwarder(rootEventSource);
eventForwarder.AnotherEvent += HandleAnotherEvent;
rootEventSource.Raise();
Console.WriteLine("done");
Console.ReadKey();
}
private static void HandleAnotherEvent()
{
Console.WriteLine("received AnotherEvent");
}
}
This results in the output:
received AnotherEvent
done
Now I make a slight change to the implementation of EventForwarder to use a method group for forwarding the event:
public EventForwarder(RootEventSource rootEventSource)
{
this.rootEventSource = rootEventSource;
this.rootEventSource.RootEvent += this.AnotherEvent.SafeInvoke;
}
The output becomes:
done
So AnotherEvent is not raised.
Until now i would have considered the two lines:
this.rootEventSource.RootEvent += this.AnotherEvent.SafeInvoke;
this.rootEventSource.RootEvent += () => this.AnotherEvent.SafeInvoke();
as being equivalent. It seems they're not.
So what is the difference? Plus why is the event not being raised?
PS: while usually R# suggests to replace () => this.AnotherEvent.SafeInvoke(); by this.AnotherEvent.SafeInvoke it doesn't do so here. So apparently it knows that it should not do it here.
When you assign a method group to event like this:
this.rootEventSource.RootEvent += this.AnotherEvent.SafeInvoke;
you in fact create a delegate from method SignalExtensions.SafeInvoke which as a parameter takes your this.AnotherEventdelegate object. Since it is initially null, you create a delegate with null parameter. This null value will of course never change, since delegates are immutable.
If you want to forward an event you should maybe do it like this:
public class EventForwarder
{
private readonly RootEventSource rootEventSource;
public EventForwarder(RootEventSource rootEventSource)
{
this.rootEventSource = rootEventSource;
}
public event Signal AnotherEvent
{
add { this.rootEventSource.RootEvent += value; }
remove { this.rootEventSource.RootEvent -= value; }
}
}

Multicast delegate and event

I am studying about delegates. Few days back, I did a sample for a multicast delegates and reviewed here My previous question and clearly understood about multicast delegate.
But now I trying to do a multicast delegate sample with a event. But I got some doubts while doing sample. In the above link, I did all functions and delegate declaration in one class and add the function in to delegate using += and just call the delegate. So all function inside delegate invoked.
But now I am doing it in two different classes and trying to do all functions with the help of a event. I am providing my current code below.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ArithmeticOperations aOperations = new ArithmeticOperations();
aOperations.StartCalculations += new ArithmeticOperations.BasicCalculations(aOperations_StartCalculations);
aOperations.PerformCalculations(20, 10);
}
void aOperations_StartCalculations(string msg)
{
MessageBox.Show(msg);
}
}
class ArithmeticOperations
{
public delegate void BasicCalculations(string msg);
public event BasicCalculations StartCalculations;
public void PerformCalculations(int n1, int n2)
{
StartCalculations("Operation Success");
}
void Add(int num1, int num2)
{
MessageBox.Show("Performing addition.");
}
void Sub(int num1, int num2)
{
MessageBox.Show("Performing substraction.");
}
void Mul(int num1, int num2)
{
MessageBox.Show("Performing multiplication.");
}
void Div(int num1, int num2)
{
MessageBox.Show("Performing division.");
}
}
Here in the Form1 is my main class and ArithmeticOperations class is using for doing functionalities. When on this statement
aOperations.PerformCalculations(20, 10);
in the Form1, the PerformCalculation() function in the ArithmeticOperations class will execute.
But my doubt is how I register all the Add, Sub, Mul and Div function to the delegate in ArithmeticOperations class to invoke all functions by just calling the delegate object and return "Operation Success" to the event callback function in Form1 class ?
Since BasicCalculations takes a single argument of type string, it cannot be used to directly invoke your methods Add, Subtract etc.
If you want PerformCalculations to invoke each of your methods via multicast, you will need a delegate of a type equivalent to Action<int, int>, eg:
class ArithmeticOperations
{
public delegate void BasicCalculations(string msg);
public event BasicCalculations StartCalculations;
private Action<int, int> calculations;
public ArithmeticOperations()
{
this.calculations += this.Add;
this.calculations += this.Sub;
}
public void PerformCalculations(int n1, int n2)
{
this.calculations.Invoke(n1, n2);
StartCalculations("Operation Success");
}
// ...
}
If you want your individual arithmetic methods to invoke your StartCalculations event with a string, you can let them do it like this:
void Add(int num1, int num2)
{
StartCalculations("Performing addition.");
}
When raising events, you should test that there are subscribers first (to avoid a NullReferenceException). The standard pattern is to get any handler subscribed, test for null then invoke the handler. This will avoid hitting a NullReferenceException, if someone unsubscribes after a testing the event for null:
void Add(int num1, int num2)
{
var handler = this.StartCalculations;
if (handler != null)
{
handler("Performing addition.");
}
}
Since that would be a lot of code repetition for each method, you can refactor it into a separate method:
void Add(int num1, int num2)
{
this.OnStartCalculation("Performing addition.");
}
void OnStartCalculation(string message)
{
var handler = this.StartCalculations;
if (handler != null)
{
handler(message);
}
}
one of the options would be creating a private event
public delegate void BasicCalculations(string msg);
public delegate void DoMath(int na, int nb);
class ArithmeticOperations
{
public ArithmeticOperations()
{
StartMath += new DoMath(ArithmeticOperations_StartMath);
}
void ArithmeticOperations_StartMath(int na, int nb)
{
Add(na, nb);
Sub(na, nb);
Mul(na, nb);
Div(na,nb);
}
public event BasicCalculations StartCalculations;
private event DoMath StartMath;
public void PerformCalculations(int n1, int n2)
{
StartMath(n1, n2);
StartCalculations("Operation Success");
}
void Add(int num1, int num2)
{
Console.WriteLine("Performing addition.");
}
void Sub(int num1, int num2)
{
Console.WriteLine("Performing substraction.");
}
void Mul(int num1, int num2)
{
Console.WriteLine("Performing multiplication.");
}
void Div(int num1, int num2)
{
Console.WriteLine("Performing division.");
}
}

Categories