I have a need to wrap a number of functions with code that will pre-process input data for these functions ("Wrapper"). Here's the code written using delegates (the code is simplified as much as possible, while still functional and immediately compilable):
namespace ConsoleApp
{
class ClientContext
{
public int i = 1;
}
class SPRemoteEventProperties
{
}
class Program
{
public delegate void Del(ClientContext clientContext, SPRemoteEventProperties properties);
public static void Wrapper(Del f, ClientContext clientContext, SPRemoteEventProperties properties)
{
ClientContext newClientContext = new ClientContext
{
i = clientContext.i * 2
};
f(newClientContext, properties);
}
static void Main(string[] args)
{
Function1(new ClientContext(), new SPRemoteEventProperties());
Function2(new ClientContext(), new SPRemoteEventProperties());
Wrapper(Function1, new ClientContext(), new SPRemoteEventProperties());
Wrapper(Function2, new ClientContext(), new SPRemoteEventProperties());
Console.Read();
}
static void Function1(ClientContext clientContext, SPRemoteEventProperties properties)
{
Console.WriteLine("Function1: " + clientContext.i);
}
static void Function2(ClientContext clientContext, SPRemoteEventProperties properties)
{
Console.WriteLine("Function2: " + clientContext.i);
}
}
}
Now, to simplify the code I'd like to rewrite it using Action<> syntax and inline the functions' code. Here's what I tried to do, but I can't manage to write a correct and functioning program:
using System;
namespace ConsoleApp
{
class ClientContext
{
public int i = 1;
}
class SPRemoteEventProperties
{
}
class Program
{
Action<ClientContext, SPRemoteEventProperties> Act;
// error in next line =>
public static void Wrapper(Act f, ClientContext clientContext, SPRemoteEventProperties properties)
{
ClientContext newClientContext = new ClientContext
{
i = clientContext.i * 2
};
f(newClientContext, properties);
}
static void Main(string[] args)
{
Function1(new ClientContext(), new SPRemoteEventProperties());
Function2(new ClientContext(), new SPRemoteEventProperties());
// error in next 2 lines =>
Wrapper((new ClientContext(), new SPRemoteEventProperties()) => Console.WriteLine("Function1: " + clientContext.i));
Wrapper((new ClientContext(), new SPRemoteEventProperties()) => Console.WriteLine("Function2: " + clientContext.i));
Console.Read();
}
static void Function1(ClientContext clientContext, SPRemoteEventProperties properties)
{
Console.WriteLine("Function1: " + clientContext.i);
}
static void Function2(ClientContext clientContext, SPRemoteEventProperties properties)
{
Console.WriteLine("Function2: " + clientContext.i);
}
}
}
Can you help me correct this code?
I'm not sure i fully understand what you are trying to do, am i close?
public static void Wrapper(ClientContext clientContext, SPRemoteEventProperties properties, Action<ClientContext, SPRemoteEventProperties> action)
{
action(newClientContext, properties);
}
...
Wrapper( new ClientContext(), new SPRemoteEventProperties(),(context, properties) => Console.WriteLine("Function1: " + context.i));
The usage of Action is different than a delegate:
While the delegate syntax define a type declaration that you can use to define a variable, the Action syntax is already the type, and you can use it to create your variable.
I'm not sure i'm clear, but, following is the correct syntax to use:
public static void Wrapper(Action<ClientContext, SPRemoteEventProperties> f, ClientCo ...
An Action has the diamond notation (<>) after it, which is a sign that it is a generic type. Unlike delegate, it contains everything the compiler needs to make it type safe. This differs from delegate which is not really typed until you define a specific delegate, which tells the compiler the types of all the parameters. With the Action you just need to put the types into the diamond and voila, it is a complete type now.
Since the parameter name is always preceded by its type, your prototype should read like this:
public static void Wrapper(Action<ClientContext, SPRemoteEventProperties> f, ClientContext clientContext, SPRemoteEventProperties properties)
{
That is everything the compiler needs to know about what is going to be contained in that argument, type-wise.
Related
Following How to decorate code in C# wiithout C/C++ - like macros I have just switched my logging system over to accept log messages as lambdas rather than as strings:
void Log(Func<string> msg)
{
if (logIsEnabled)
{
Debug.Write(msg());
}
}
My understanding is that this is much better, performance-wise, in that if I write:
Log(() => "foo(" + myInteger + ")");
then the string foo(42) is constructed only if logIsEnabled. For the sake of this question, let's assume that Log() is being called at high frequency, and constructing these strings for the log comes at an undesirable cost.
Suddenly I worried, though - is a lambda being instantiated every time this line of code is reached? Might that be more of a performance cost than constructing a string? I'm not clear on what's happening here, under the hood.
So, my question is: how is the lambda actually implemented? Is it constructed at compile-time and passed just as a pointer to a function? Is it constructed on first use and on subsequent passes just a pointer? Or is it constructed every time the line of code executes?
is a lambda being instantiated every time this line of code is reached?
Yes. Not only a new Func<string> is instantiated every time, but also a small compiler-generated class. Let's post some code to SharpLab, and see what comes out:
using System;
class Program
{
static bool logIsEnabled;
static void Main()
{
logIsEnabled = true;
int myInteger = 13;
Log(() => "foo(" + myInteger + ")");
}
static void Log(Func<string> msg)
{
if (logIsEnabled)
{
Console.WriteLine(msg());
}
}
}
SharpLab output (sanitized):
using System;
using System.Runtime.CompilerServices;
internal class Program
{
[CompilerGenerated]
private sealed class DisplayClass
{
public int myInteger;
internal string M()
{
return string.Concat("foo(", myInteger.ToString(), ")");
}
}
private static bool logIsEnabled;
private static void Main()
{
DisplayClass displayClass = new DisplayClass();
logIsEnabled = true;
displayClass.myInteger = 13;
Log(new Func<string>(displayClass.M));
}
private static void Log(Func<string> msg)
{
if (logIsEnabled)
{
Console.WriteLine(msg());
}
}
}
The DisplayClass is the closure that the compiler had to generate, in order to hold the myInteger variable. This variable is hoisted to a public field of the DisplayClass class.
The Visual Studio can help you at detecting that a variable has been captured. Just hover the mouse over the lambda operator (=>).
It is possible to avoid the allocation of the two objects by passing the myInteger as an argument, instead of relying on the convenience of captured variables and closures. Here is how:
using System;
class Program
{
static bool logIsEnabled;
static void Main()
{
logIsEnabled = true;
int myInteger = 13;
Log(arg => "foo(" + arg + ")", myInteger);
}
static void Log<TArg>(Func<TArg, string> msg, TArg arg)
{
if (logIsEnabled)
{
Console.WriteLine(msg(arg));
}
}
}
SharpLab output (sanitized):
using System;
using System.Runtime.CompilerServices;
internal class Program
{
[Serializable]
[CompilerGenerated]
private sealed class C
{
public static readonly C singleton = new C();
public static Func<int, string> lambda;
internal string M(int arg)
{
return string.Concat("foo(", arg.ToString(), ")");
}
}
private static bool logIsEnabled;
private static void Main()
{
logIsEnabled = true;
int arg = 13;
Log(C.lambda ?? (C.lambda = new Func<int, string>(C.singleton.M)), arg);
}
private static void Log<TArg>(Func<TArg, string> msg, TArg arg)
{
if (logIsEnabled)
{
Console.WriteLine(msg(arg));
}
}
}
Now the compiler generated a singleton (the C class), and the Func<TArg, string> is instantiated only once per TArg type. So if your program uses the Log<TArg> with ints, strings and decimals, only a Func<int, string>, a Func<string, string> and a Func<decimal, string> will be created in total, irrespective of how many times the Log<TArg> will be invoked.
In case you want to pass more than one arguments to the Log method, you'll have to write additional Log<TArg1, TArg2>, Log<TArg1, TArg2, TArg3> etc overloads.
I have a test like this:
delegate int Mapper(string str);
public class MappingTests
{
[Fact]
public void MapFromStringToInt()
{
Mapper stringToIntMapper = (string text) => { return int.Parse(text); };
var x = stringToIntMapper("1");
Assert.Equal(1, x);
}
}
I can move the declaration of stringToIntMapper outside the test class like this:
delegate int Mapper(string str);
Mapper stringToIntMapper = (string text) => { return int.Parse(text); };
public class MappingTests
{
[Fact]
public void MapFromStringToInt()
{
var x = stringToIntMapper("1");
Assert.Equal(1, x);
}
}
How can I access stringToIntMapper in my test (or any other class/method)? Now the compiler says it cannot be resolved.
You can't declare variables outside of a class. If you want your variable to be globally accessable, you can put it into a static class:
public static class DelegateCollection
{
public static readonly Mapper stringToIntMapper = (string text) => { return int.Parse(text); };
}
You can access it via
DelegateCollection.stringToIntMapper
But you should make sure that your static class is stateless, e.g. by making everything inside this class readonly. Otherwise, your code becomes hard to test and you might get bugs which are hard to find.
Just make it a method:
static int StringToIntMapper(string text) {
return int.Parse(text);
}
I'm making a networking application where I want to implement strongly typed RPC. As result I'd like to be able to pass methods no matter the parameters so I can get them and store them in a dictionary so I can construct request parameters properly as well once a packet arrive I am able to read it using the parameters the same remote method used
I'd like something like this:
Register(Enum key, [method with unknown parameters])
In C# 8.0 using generics and Delegate Constraint you can do something like this:
using System;
using System.Collections.Generic;
namespace ConsoleApp
{
public class Program
{
public static void Main(params string[] args)
{
var app = new NetworkingApplication();
app.Register<Action<int, string>>(PacketType.Type1, Method1, 1, "string1 argument");
app.Register<Func<string, string>>(PacketType.Type2, Method2, "string2 argument");
app.OnPacketReceived(PacketType.Type1);
app.OnPacketReceived(PacketType.Type2);
}
public static void Method1(int arg1, string arg2)
{
Console.WriteLine($"Method1 Invoked with args: {arg1}, {arg2}");
}
public static string Method2(string arg1)
{
Console.WriteLine($"Method2 Invoked with args: {arg1}");
return "Foo";
}
}
public class NetworkingApplication
{
private readonly IDictionary<PacketType, DelegateInvoker> _registrations;
public NetworkingApplication()
{
_registrations = new Dictionary<PacketType, DelegateInvoker>();
}
public void Register<TDelegate>(PacketType packetType, TDelegate #delegate, params object[] args)
where TDelegate : Delegate
{
_registrations[packetType] = new DelegateInvoker(#delegate, args);
}
//invoke this when the packet is received
public void OnPacketReceived(PacketType type)
{
if (_registrations.TryGetValue(type, out var invoker))
{
invoker.Invoke();
}
}
private class DelegateInvoker
{
public DelegateInvoker(Delegate #delegate, object[] args)
{
Delegate = #delegate;
Arguments = args;
}
private Delegate Delegate { get; }
private object[] Arguments { get; }
public void Invoke()
{
Delegate.Method.Invoke(Delegate.Target, Arguments);
}
}
}
public enum PacketType
{
Type1,
Type2,
Type3
}
}
As mentioned above you can use MethodInfo, it belongs to the System.Reflection namespace. To do this, first get the Type type of the object like this:
var type = obj.GetType()
After this you can use var methods = type.GetMethods(). This will give you an MethodInfo[]. Search the element, using your favorite method for doing so. Such as Linq:
var method = methods.Where(it => it.Name == __yourName__).LastOrDefault();
*where yourName is the name of your method.
Now you have the method you are looking for. Get the parameters using
var parameters = method.getParameters();
And there are the parameters as ParameterInfo[].
From there you can get the type of each parameter using the parameter.ParameterType property.
This being said be very careful with Reflection, it is very, very powerful but can decrease performance heavily, when overused.
Have look at it System.Reflection namespace here .
You can now add the method to a collection, such as a dictionary:
var dictionary = new Dictionary<int,MethodInfo>();
dictionary.Add(1, method);
And retrieve it like this:
var method = dictionary[1];
To call the function you can user method.Invoke() and pass in the parameters as need.
EDIT:
If you would like to send the parameters as well as the function over a network. You could create a new class that serves as a DTO Data Transfer Object. This class could have as property an Array of parameters (ParameterInfo[]), the MethodInfo and anything else you want.
You could then serialize the object (maybe json) and send it to another system, that could then deserialize it, and Invoke the MethodInfo obj
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
public class Program
{
public static void Main(params string[] args)
{
var app = new NetworkingApplication();
app.Register(PacketType.Type1, () =>
{
Console.WriteLine("Type1 Packet is received!");
});
}
}
public class NetworkingApplication
{
private readonly IDictionary<PacketType, Action> _registrations;
public NetworkingApplication()
{
_registrations = new Dictionary<PacketType, Action>();
}
public void Register(PacketType packetType, Action method)
{
_registrations[packetType] = method;
}
//invoke this when the packet is received
public void OnPacketReceived(PacketType type)
{
if (_registrations.TryGetValue(type, out var action))
{
action?.Invoke();
}
}
}
public enum PacketType
{
Type1,Type2,Type3
}
}
I am not able to invoke a delegate when passing an anonymous function.
Here is the code.
public class MethodCollection
{
public static void Print(Action<int, int> printNumbers)
{
}
}
public class Program
{
static void Main(string[] args)
{
MethodCollection.Print((p, q) => { p = q = 3; Console.WriteLine(p + q); });
Console.ReadLine();
}
}
The output is a blank screen.
The program does not print the expected output, i.e. 6.
Just call printNumbers.Invoke(3,3); or so in your Print method.
You should remove the p = q = 3; in your action because as Jon mentioned this will make your action ignore the numbers you pass the action.
Or it can be something like this... and I think you should see delegates basics first
helpful link to learn basics of delegates
public class MethodCollection
{
public static void Print(Action<int, int> printNumbers)
{
printNumbers.Invoke(0,0);
}
}
public class Program
{
static void Main(string[] args)
{
MethodCollection.Print((p, q) => { Console.WriteLine((p=3) + (q=3)); });
Console.ReadLine();
}
}
I hope this is a simple question. I'm building a simple console application in C#. I have a class:
using System;
using Filter;
public class Params
{
public string key;
public bool distinct;
public List<string> fields;
public string filter;
public int limit;
public int skip;
public bool total;
public List<Tuple<string, GroupType>> group;
public List<Tuple<string, OrderType>> order;
public Params()
{
key = "";
distinct = false;
fields = new List<string>();
filter = "";
group = new List<Tuple<string, GroupType>>();
limit = 0;
order = new List<Tuple<string, OrderType>>();
skip = 0;
total = false;
}
public void AddGroup(string field, GroupType type)
{
group.Add(new Tuple<string, GroupType>(field, type));
}
public void AddOrder(string field, OrderType type)
{
order.Add(new Tuple<string, OrderType>(field, type));
}
}
My program .cs class is:
namespace csharpExample
{
class Program
{
public static void Main(string[] args)
{
Params p = new Params();
Console.WriteLine("Test");
}
}
}
I want to use Params in my program.cs class where Main() is called. I thought I could simply use Params like above. I've also tried to do a using Params; both of these are errors in VS since it can't find the directive. I've also tried adding my own namespace: namespace MyNameSpace; around my Params class. When I do this I still am unable to do a using MyNameSpace; statement as it can't find it.
I just want to extract out a bunch of functions into a class that I can reuse. How do i call this class once it's created?
-Thanks
Thanks for the help.
If you want to access the Params object in the Main function, just add Params p = new Params (); to the Main function at the top.
Most likely your problem is that Main is static, meaning that it can't access other things that aren't static which are outside of it. If you declared Params in the Program class, unless you made it static, it can't be accessed in Main.
Are you talking about calling the constructor or the properties you are setting? You can set the class at the top of your base class and then call the instance of it. But since it is a static class you should probably use a helper method in the main.
namespace Example
{
public class Program
{
Params p = new Params();
string writefromParams() // I exist just to give the string back from params with a nonstatic method
{
return p.key;
}
static void Main(string[] args)
{
Program p2 = new Program(); // set up a new instance of this very class
Console.WriteLine(p2.writefromParams()); // get non static method from class
Console.ReadLine();
}
}
}