Related
I am trying to understand the concept of lambda expression,extension method,Linq and IEnumerable interface. You can guess that i am farely new to c sharp.Here i've come up with a problem which will incorporate all the above mentioned concepts . Here i have a list which contain three object.I want to change the name property of a Students object in a specified index .I wrote an extension method which accept a callback function.Callback function accepts an integer index and a new Name string. It should change the name property and return the object .But my code failed to do so as i am not sure how to pass parameter to Func callback in extension method.I am in need of some assistant to understand the problem and fix errors from my code ?
class Program
{
static void Main(string[] args)
{
List<Students> students = new List<Students>();
students.Add(new Students(111443, "sakib"));
students.Add(new Students(111445, "zami"));
students.Add(new Students(111444, "habib"));
var student = students.First();
var changed1 = students.Change((int num,string newname) => { return students[num].s_name = newname;});
}
}
public class Students
{
public int s_id;
public string s_name;
public Students(int id, string name)
{
this.s_id = id;
this.s_name = name;
}
}
public static class LinqHelper
{
public static IEnumerable<T> Change<T> (this IEnumerable<T> source, Func<int,string,T> callback)
{
var myList = new List<Students>();
myList.Add(callback(1,"zami")); // i was passing parameter here which is not so helpful i guess !
return myList;
}
}
The Func < int, string, T > denotes a function that accepts an integer and string as inputs and T as the return type. The anonymous function you have used has a return type of "string":
var changed1 = students.Change((int num,string newname) => { return students[num].s_name = newname;});
You should return the student instance from the function to make it work. Try replacing the above code with the following:
var changed1 = students.Change((int index, string newname) =>
{
var studentObj = students[index];
studentObj.s_name = newname;
return studentObj;
});
To allow the LinqHelper to accept the index and argument, use the following:
public static class LinqHelper
{
public static IEnumerable<T> Change<T>(this IEnumerable<T> source, Func<int, string, T> callback, int index, string argument)
{
var myList = new List<T>();
myList.Add(callback(index, argument)); // i was passing parameter here which is not so helpful i guess !
return myList;
}
}
And then, you could invoke the method as follows:
var changed1 = students.Change((int index, string newname) =>
{
var studentObj = students[index];
studentObj.s_name = newname;
return studentObj;
},
1,
"zami");
You haven't created the lambda that evaluated to Func<int, string, T>. Your call to Change extension should look like:
var changed1 = students.Change((num, newnam) => {
students[num].s_name = newnam;
return students[num];
});
(you should return T as Func requires).
I need help. I'm making a programm and i have a problem.
I have a constructor with delegate already:
public delegate bool delIsDone(int curState, int needState);
public Progress(delIsDone doneFunction) { ... }
I need to pass it without creating it outside of creation:
public bool check() { return true }
Progress p = new Progress(check);
I need to do something like this:
Progress p = new ProgressChecker(bool check(int currentProgress, int needProgress) {
return currentProgress < needProgress;
});
Where ProgressChecker is a class that have method that check progress.
In loop i execute this function to get result. If function return "true" it's mean that "Achievement geted" and i need to hold it.
Thx for help
In the constructor you can pass in a function like so:
ProgressChecker(Func<int,int,bool> checker)
That means you can pass a function into the constructor for ProgressChecker
public bool checker (int a, int b)
{
return a < b;
}
var t = new ProgressChecker(checker);
EDIT:
If you want to use the delegate, then in the ProgressChecker class, the constructor must take in that delegate type:
private delIsDone delIsDone;
public ProgressChecker(delIsDone delIsDone)
{
this.delIsDone = delIsDone;
}
You can the pass a delegate instance like so:
public class Program
{
public delegate bool delIsDone(int curState, int needState);
public static bool checkNumbers(int a, int b)
{
return a < b;
}
public static void Main(string[] args)
{
var t = new ProgressChecker(new delIsDone(checkNumbers));
// OR var t = new ProgressChecker(new delIsDone((a, b) => { return a < b; }));
}
}
You can also create Expression Trees, it is my snippet for it (it can be easly converted to your example)
//easy way
Expression<Action<int>> printExpr = (arg) => Console.WriteLine(arg);
printExpr.Compile()(10);
//hard way
ParameterExpression param = Expression.Parameter(typeof(int), "arg");
MethodCallExpression methodCall = Expression.Call
(
typeof(Console).GetMethod("WriteLine", new[]
{
typeof(int)
}
),
param
);
Expression.Lambda<Action<int>>(methodCall, param).Compile()(10); //execution
More information ca be gathered at Generating Dynamic Methods with Expression Trees
Let's say I have the following class.
MyClass<T>
{
public void MyMethod(T a, List<T> b, List<Tuple<T, string>> c) {}
}
I can get the type of the arguments of the method as follow
Type testType = typeof(MyClass<>);
MethodInfo myMethodInfo = testType.GetMethod("MyMethod");
Type[] paramTypes = myMethodInfo.GetParameters().Select(pi => pi.ParameterType);
How can I manually create an array containing the same open types as paramTypes from a string? For ex from
var typesAsStr = new string[] {"T", "List`1[T]", "List`1[Tuple`2[T, string]]"};
If I had MyClass<int>, I could do something like Type.GetType(fullQualifiedNameOfArg) for each argument, but here I want to keep the generic argument T:
I can't create "a": I can't do Type.GetType("T")
I can almost create "b": I can do Type.GetType("List `1"), but the info on "T" is not yet present
I don't know how to create "c"
I ended up needing this when converting a Mono.Cecil type into a .net type: Cecil gives me the info on a method named "MyMethod" with arguments "T", "List<T>" and "List<Tuple<T, string>>". I then want to get that method using reflection (if there are several methods with the same name and argument numbers, I have to check the args to know which one it is), that's why I'd want to have a way to transform what Cecil tells me into what .Net knows, to be able to compare with what's in paramTypes.
I've also seen several other people asking how to convert a Mono.Cecil type into a .Net one, so that's also why I thought I'd try.
You can get T using strings, you do it by calling GetType with the string name of MyClass and then getting the generic arguments of the resulting type. From there you can build up the other open generic types using MakeGenericType. You have to work from the inside out by constructing the most nested types first. To do it automatically across differing methods would require some string parsing to get to the nested types. For the sake of comparing .Net methods against Cecil methods, #Tengiz might have a better approach.
To run the code, update the string name of MyClass to have the correct namespace for your environment.
private static void Main(string[] args) {
// change 'yournamespace'
Type testType = Type.GetType("yournamespace.MyClass`1");
Type[] testTypeGenericArgs = testType.GetGenericArguments();
// Get T type from MyClass generic args
Type tType = testTypeGenericArgs[0];
Type genericListType = Type.GetType("System.Collections.Generic.List`1");
// create type List<T>
Type openListType = genericListType.MakeGenericType(testTypeGenericArgs[0]);
Type genericTuple = Type.GetType("System.Tuple`2");
Type stringType = Type.GetType("System.String");
// create type Tuple<T, string>
Type openTuple = genericTuple.MakeGenericType(new[] { tType, stringType });
// create type List<Tuple<T, string>>
Type openListOfTuple = genericListType.MakeGenericType(openTuple);
Type[] typesFromStrings = new[] { tType, openListType, openListOfTuple };
// get method parameters per example
Type myClassType = typeof(MyClass<>);
MethodInfo myMethodInfo = myClassType.GetMethod("MyMethod");
Type[] paramTypes = myMethodInfo.GetParameters().Select(pi => pi.ParameterType).ToArray();
// compare type created from strings against types
// retrieved by reflection
for (int i = 0; i < typesFromStrings.Length; i++) {
Console.WriteLine(typesFromStrings[i].Equals(paramTypes[i]));
}
Console.ReadLine();
}
I found this so interesting, that I had to create something myself, and present it to the world... and after a couple hours of exploration, here is what I got...
The extension method for Type: GetMethodByString
This is very simple: get a type and then call the method passing a string that represents the method you want:
var type = typeof(MyType<>);
type.GetMethodByString("MyMethod(T, List`1[T], List`1[Tuple`2[T, String]])")
Sample program
class Program
{
public static void Main()
{
var t1 = typeof(MyType<>);
var mi11 = t1.GetMethodByString("MyMethod(T, List`1[T], List`1[Tuple`2[T, String]])");
var mi12 = t1.GetMethodByString("Method[X](X, T)");
var mi13 = t1.GetMethodByString("Method(List`1[T], Int32 ByRef)");
var t2 = typeof(MyType);
var mi21 = t2.GetMethodByString("Method[X, T](List`1[X], Tuple`2[X, List`1[T]])");
}
class MyType<T>
{
public void MyMethod(T a, List<T> b, List<Tuple<T, string>> c) { }
public void Method(List<T> t, out int i) { i = 0; }
public void Method<X>(X x, T t) { }
}
class MyType
{
public int Method<X, T>(List<X> x, Tuple<X, List<T>> tuple)
{
return 1;
}
}
}
TypeExtensions
public static class TypeExtensions
{
public static MethodInfo GetMethodByString(
this Type type, string methodString)
{
return type.GetMethods()
.Where(mi => MethodToString(mi) == methodString)
.SingleOrDefault();
}
public static string MethodToString(MethodInfo mi)
{
var b = new StringBuilder();
b.Append(mi.Name);
if (mi.IsGenericMethodDefinition)
b.AppendFormat("[{0}]",
string.Join(", ", mi.GetGenericArguments()
.Select(TypeToString)));
b.AppendFormat("({0})", string.Join(", ", mi.GetParameters()
.Select(ParamToString)));
return b.ToString();
}
public static string TypeToString(Type t)
{
var b = new StringBuilder();
b.AppendFormat("{0}", t.Name);
if (t.IsGenericType)
b.AppendFormat("[{0}]",
string.Join(", ", t.GetGenericArguments()
.Select(TypeToString)));
return b.ToString();
}
public static string ParamToString(ParameterInfo pi)
{
return TypeToString(pi.ParameterType).Replace("&", " ByRef");
}
}
Why I didn't try to get types by name
Unfortunately, I found no way to get a type given a string, unless you guess a lot about the type being represented... so, it is quite impossible.
That explains why I did a method to find the method instead. It is much more precise... but it could eventually fail, in very rare and bizarre circumstances:
if you create a List of your own, and then two overloads of the same method, one taking the .Net List and the other taking the List you have created... then it fails
Why not parsing the input string
I found that for the purpose of looking up a method, it is enough to have a fixed syntax string, so that I can generate it from the method and compare... that have some limitations:
must use the name of the type, so C# alliases won't work (string must be named "String", int must be named "Int32" not "int")
EDIT
Performance
This solution is not very performatic, but nothing that a cache cannot solve. The method could use a dictionary, using both the Type and the string as a composite key, and look in there before trying to find the method by bulding a lot of strings and comparing all of them.
If you need thread safety on the cache dictionary, use a ConcurrentDictionary<TKey, TValue>... very nice class.
EDIT 2: Created a cached version
static ConcurrentDictionary<Type, Dictionary<string, MethodInfo>> cacheOfGetMethodByString
= new ConcurrentDictionary<Type, Dictionary<string, MethodInfo>>();
public static MethodInfo GetMethodByString(
this Type type, string methodString)
{
var typeData = cacheOfGetMethodByString
.GetOrAdd(type, CreateTypeData);
MethodInfo mi;
typeData.TryGetValue(methodString, out mi);
return mi;
}
public static Dictionary<string, MethodInfo> CreateTypeData(Type type)
{
var dic = new Dictionary<string, MethodInfo>();
foreach (var eachMi in type.GetMethods())
dic.Add(MethodToString(eachMi), eachMi);
return dic;
}
Hoppe this helps! =)
I don't think .NET allows you to create a type "T" where T is a type argument, which is yet to be specified. So, the array of Type(s) from input string array cannot be created.
However, in the second part of your question, I read that you want to identify the method which has those types given as string. That task is solvable by iterating though the arguments, creating another array of strings describing the method arguments, and then comparing the resulting and input arrays, as follows:
class MyClass<T>
{
public void MyMethod(T a, List<T> b, List<Tuple<T, string>> c) { }
}
class Program
{
static void Main(string[] args)
{
//input.
var typesAsStr = new string[] { "T", "List`1[T]", "List`1[Tuple`2[T, string]]" };
//type to find a method.
Type testType = typeof(MyClass<>);
//possibly iterate through methods instead?
MethodInfo myMethodInfo = testType.GetMethod("MyMethod");
//get array of strings describing MyMethod's arguments.
string[] paramTypes = myMethodInfo.GetParameters().Select(pi => TypeToString(pi.ParameterType)).ToArray();
//compare arrays of strings (can be improved).
var index = -1;
Console.WriteLine("Method found: {0}", typesAsStr.All(str => { index++; return index < paramTypes.Length && str == paramTypes[index]; }));
Console.ReadLine();
}
private static CSharpCodeProvider compiler = new CSharpCodeProvider();
private static string TypeToString(Type type)
{
if (type.IsGenericType) {
return type.Name + "[" + string.Join(", ", type.GetGenericArguments().Select(ga => TypeToString(ga))) + "]";
}
else if (type.IsGenericParameter) {
return type.Name;
}
//next line gives "string" (lower case for System.String).
//additional type name translations can be applied if output is not what we neeed.
return compiler.GetTypeOutput(new CodeTypeReference(type));
}
}
In the [console] output I see that your input string matches the function.
BTW, a lot of optimizations can be applied to this code if you face the performance problems, such as efficient way of working with strings, releasing CSharpCodeProvider instance maybe, etc. But the code is enough to solve the given task as questioned.
You cannot do what you are trying to do, but there is a relatively easy way of achieving the same result by entering from a different direction
Strings do not identify types uniquely
This is the basic problem with converting strings to types: when you see a T, you have no idea where it came from. The following is a valid class definition:
class Simple<T> {
public T Make(T blah) {
return blah;
}
public T Make<T>(T blah) {
return blah;
}
}
Two overloads of Make have parameters that look identical, yet they do not compare as equal. Moreover, there is absolutely no way of getting the T of the generic Make<T> without first getting the MethodInfo for the generic Make<T> - a circular dependency.
What can you do?
Instead of going for the impossible string->Type conversion, you can build a matcher that tells you if an instance of a type, including an unbounded generic type, matches a given string representation:
static bool MatchType(string str, Type type)
With this method in hand, you can walk through all available methods with a particular name, and check the types of their parameter lists one by one against the strings in your array of strings:
var typesAsStr = new [] {"T", "List`1[T]", "List`1[Tuple`2[T, string]]"};
var myMethod = typeof (Simple<>)
.GetMethods()
.SingleOrDefault(m => m.Name == "MyMethod" &&
typesAsStr
.Zip(m.GetParameters(), (s, t) => new {s, t})
.All(p => MatchType(p.s, p.t.ParameterType))
);
How do you implement the MatchType method?
You can use a technique similar to Recursive Descent Parsing: tokenize your string, and then match elements of your type as you go through the chain of tokens. When a class is parameterized, get generic parameters and match them recursively. You need to pay attention to array types, but that is relatively simple as well. Take a look:
public static bool MatchType(string str, Type type) {
var queue = new Queue<Token>(Tokenize(str));
return MatchRecursive(queue, type) && (queue.Count == 0);
}
private static bool MatchRecursive(Queue<Token> tokens, Type type) {
string baseName;
if (!ReadToken(tokens, TokenType.Identifier, out baseName)) return false;
var ranks = new List<int>();
while (type.IsArray) {
ranks.Add(type.GetArrayRank());
type = type.GetElementType();
}
if (type.IsGenericType) {
if (!type.Name.StartsWith(baseName+"`") || !DropToken(tokens, TokenType.Tick)) return false;
string numStr;
int num;
if (!ReadToken(tokens, TokenType.Number, out numStr)
|| !int.TryParse(numStr, out num)
|| !DropToken(tokens, TokenType.OpenBraket)) return false;
var genParams = type.GetGenericArguments();
if (genParams.Length != num) return false;
for (var i = 0 ; i < num ; i++) {
if (i != 0 && !DropToken(tokens, TokenType.Comma)) return false;
if (!MatchRecursive(tokens, genParams[i])) return false;
}
if (!DropToken(tokens, TokenType.CloseBraket)) return false;
}
foreach (var rank in ranks) {
if (!DropToken(tokens, TokenType.OpenBraket)) return false;
for (var i = 0 ; i != rank-1 ; i++) {
if (!DropToken(tokens, TokenType.Comma)) return false;
}
if (!DropToken(tokens, TokenType.CloseBraket)) return false;
}
return type.IsGenericType || Aliases.Contains(new Tuple<string, Type>(baseName, type)) || type.Name == baseName;
}
private static readonly ISet<Tuple<string,Type>> Aliases = new HashSet<Tuple<string, Type>> {
new Tuple<string, Type>("bool", typeof(bool)),
new Tuple<string, Type>("byte", typeof(byte)),
new Tuple<string, Type>("sbyte", typeof(sbyte)),
new Tuple<string, Type>("char", typeof(char)),
new Tuple<string, Type>("string", typeof(string)),
new Tuple<string, Type>("short", typeof(short)),
new Tuple<string, Type>("ushort", typeof(ushort)),
new Tuple<string, Type>("int", typeof(int)),
new Tuple<string, Type>("uint", typeof(uint)),
new Tuple<string, Type>("long", typeof(long)),
new Tuple<string, Type>("ulong", typeof(ulong)),
new Tuple<string, Type>("float", typeof(float)),
new Tuple<string, Type>("double", typeof(double)),
new Tuple<string, Type>("decimal", typeof(decimal)),
new Tuple<string, Type>("void", typeof(void)),
new Tuple<string, Type>("object", typeof(object))
};
private enum TokenType {
OpenBraket,
CloseBraket,
Comma,
Tick,
Identifier,
Number
}
private class Token {
public TokenType Type { get; private set; }
public string Text { get; private set; }
public Token(TokenType type, string text) {
Type = type;
Text = text;
}
public override string ToString() {
return string.Format("{0}:{1}", Enum.GetName(typeof(TokenType), Type), Text);
}
}
private static bool DropToken(Queue<Token> tokens, TokenType expected) {
return (tokens.Count != 0) && (tokens.Dequeue().Type == expected);
}
private static bool ReadToken(Queue<Token> tokens, TokenType expected, out string text) {
var res = (tokens.Count != 0) && (tokens.Peek().Type == expected);
text = res ? tokens.Dequeue().Text : null;
return res;
}
private static IEnumerable<Token> Tokenize(IEnumerable<char> str) {
var res = new List<Token>();
var text = new StringBuilder();
foreach (var c in str) {
var pos = "[],`".IndexOf(c);
if ((pos != -1 || char.IsWhiteSpace(c)) && text.Length != 0) {
res.Add(new Token(
char.IsDigit(text[0]) ? TokenType.Number : TokenType.Identifier
, text.ToString())
);
text.Clear();
}
if (pos != -1) {
res.Add(new Token((TokenType)pos, c.ToString(CultureInfo.InvariantCulture)));
} else if (!char.IsWhiteSpace(c)) {
text.Append(c);
}
}
if (text.Length != 0) {
res.Add(new Token(
char.IsDigit(text[0]) ? TokenType.Number : TokenType.Identifier
, text.ToString())
);
}
return res;
}
It is not quite clear to me what the exactly you need, but i believe you can use the following technique:
object[] parameters = CreateParameters(typeof(MyClass<>), "MyMethod", typeof(int));
Debug.Assert(parameters[0] is int);
Debug.Assert(parameters[1] is List<int>);
Debug.Assert(parameters[2] is List<Tuple<int, string>>);
//...
object[] CreateParameters(Type type, string methodName, Type genericArgument) {
object[] parameters = null;
MethodInfo mInfo = type.GetMethod(methodName);
if(mInfo != null) {
var pInfos = mInfo.GetParameters();
parameters = new object[pInfos.Length];
for(int i = 0; i < pInfos.Length; i++) {
Type pType = pInfos[i].ParameterType;
if(pType.IsGenericParameter)
parameters[i] = Activator.CreateInstance(genericArgument);
if(pType.IsGenericType) {
var arguments = ResolveGenericArguments(pType, genericArgument);
Type definition = pType.GetGenericTypeDefinition();
Type actualizedType = definition.MakeGenericType(arguments);
parameters[i] = Activator.CreateInstance(actualizedType);
}
}
}
return parameters;
}
Type[] ResolveGenericArguments(Type genericType, Type genericArgument) {
Type[] arguments = genericType.GetGenericArguments();
for(int i = 0; i < arguments.Length; i++) {
if(arguments[i].IsGenericParameter)
arguments[i] = genericArgument;
if(arguments[i].IsGenericType) {
var nestedArguments = ResolveGenericArguments(arguments[i], genericArgument);
Type nestedDefinition = arguments[i].GetGenericTypeDefinition();
arguments[i] = nestedDefinition.MakeGenericType(nestedArguments);
}
}
return arguments;
}
I have MethodBases for two functions:
public static int Add(params int[] parameters) { /* ... */ }
public static int Add(int a, int b) { /* ... */ }
I have a function that calls the MethodBases via a class I made:
MethodBase Method;
object Target;
public object call(params object[] input)
{
return Method.Invoke(Target, input);
}
Now if I AddTwoMethod.call(5, 4); it works fine.
If I however use AddMethod.call(5, 4); it returns:
Unhandled Exception: System.Reflection.TargetParameterCountException: parameters do not match signature
Is there any way to make it so that both calls work fine without need for manually putting the arguments in an array for the params int[]?
You could modify your call method to detect the params parameter and convert the rest of the input to a new array. That way your method could act pretty much the same as the logic C# applies to the method calling.
Something i quicly constructed for you (be aware that i tested this method in a pretty limited way, so there might be errors still):
public object call(params object[] input)
{
ParameterInfo[] parameters = Method.GetParameters();
bool hasParams = false;
if (parameters.Length > 0)
hasParams = parameters[parameters.Length - 1].GetCustomAttributes(typeof(ParamArrayAttribute), false).Length > 0;
if (hasParams)
{
int lastParamPosition = parameters.Length - 1;
object[] realParams = new object[parameters.Length];
for (int i = 0; i < lastParamPosition; i++)
realParams[i] = input[i];
Type paramsType = parameters[lastParamPosition].ParameterType.GetElementType();
Array extra = Array.CreateInstance(paramsType, input.Length - lastParamPosition);
for (int i = 0; i < extra.Length; i++)
extra.SetValue(input[i + lastParamPosition], i);
realParams[lastParamPosition] = extra;
input = realParams;
}
return Method.Invoke(Target, input);
}
Be aware that i tested this method in a pretty limited way, so there might be errors still.
Supposing we have the following example class:
public class Test
{
public static int Add(int i1, int i2)
{
return i1 + i2;
}
public static int Add(params int[] ints)
{
int sum = 0;
foreach (int i in ints)
sum += i;
return sum;
}
}
To get the MethodInfo objects for each overload of the static Add method you should do the following:
MethodInfo Add2Ints = typeof(Test).GetMethod("Add", new Type[] { typeof(int), typeof(int) });
MethodInfo AddParamsInts = typeof(Test).GetMethod("Add", new Type[] { typeof(int[]) });
In order to invoke any of the two methods, symply pass the arguments with the exact type expected by the specific overload you are invoking:
Add2Ints.Invoke(null, new object[] { 1, 2 });
AddParamsInts.Invoke(null, new object[] { new int[] { 1, 2 } });
Note that the following will not work:
AddParamsInts.Invoke(null, new object[] { 1, 2 });
because the signature of AddParmsInt is really (int[]) and although the compiler, as a courtesy, allows you to call such method as (int, int) under the hood what is really happening is that the call is converted for you at the call site to the equivalent (int[]) call. Via reflection you don't have the compiler's "help" so you need to pass the exact argument type defined by the method's signature.
With all that said, your call method should be as follows:
public object call(params object[] input)
{
return AddParamsInts.Invoke(null /*static*/, new object[] { input.Cast<int>().ToArray() });
}
Note that you can not directly cast a object[] array to a int[] array: int[] ints = (int[])input. Casting reference typed arrays to value-type arrays is not allowed.
Also important to note is that the defined overloads of the Add method are useless, as they overlap. Consider only using the params overload or, in case you want to guarantee that at least two arguments are needed in order to evaluate an addition, overload them the following way:
public int Add(int i1, int i2) { }
public int Add(int i1, int i2, params int[] args) { }
You should wrap the arguments in an array, but the compiler will be confused, so you need to help it a bit:
Eg:
AddMethod.call((object) new int[] {5, 4 });
I'm trying to check whether a given type is an action delegate, regardless of the amount of parameters.
The following code is the only way I know how to do this.
public static bool IsActionDelegate( this Type source )
{
return source == typeof( Action ) ||
source.IsOfGenericType( typeof( Action<> ) ) ||
source.IsOfGenericType( typeof( Action<,> ) ) ||
....
source.IsOfGenericType( typeof( Action<,,,,,,,,,,,,,,,> ) );
}
IsOfGenericType() is another extension method of mine, which does what it says, it checks whether the type is of the given generic type.
Any better suggestions?
If you are just after the delegates that have a void return type you could do the following:
public static bool IsActionDelegate(Type sourceType)
{
if(sourceType.IsSubclassOf(typeof(MulticastDelegate)) &&
sourceType.GetMethod("Invoke").ReturnType == typeof(void))
return true;
return false;
}
This would not distinguish between Action and MethodInvoker (or other void delegates for that matter) though. As other answers suggest you could examine the type name, but that kinda smells ;-)
It would help if you could clarify for what reason you want to identify Action delegates, to see which approach would work best.
static Type[] _actionTypes = new[]{
typeof(Action),
typeof(Action<>),
typeof(Action<,>),
typeof(Action<,,>),
typeof(Action<,,,>),
typeof(Action<,,,,>),
typeof(Action<,,,,,>),
typeof(Action<,,,,,,>),
typeof(Action<,,,,,,,>),
typeof(Action<,,,,,,,,>),
typeof(Action<,,,,,,,,,>),
typeof(Action<,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,,,,>)
};
private static bool IsAction(Delegate d)
{
return d != null && Array.IndexOf(_actionTypes, d.GetType()) != -1;
}
This seems to work:
private static bool IsActionDelegate(this Type source)
{
var type = source.Name;
return source.Name.StartsWith("System.Action");
}
Example:
public static class Test
{
public static bool IsActionDelegate(this Type source)
{
var type = source.Name;
return source.Name.StartsWith("Action");
}
}
class Program
{
static void Main(string[] args)
{
Action<string> one = s => { return; };
Action<int, string> two = (i, s) => { return; };
Func<int, string> function = (i) => { return null; };
var single = one.GetType().IsActionDelegate();
var dueces = two.GetType().IsActionDelegate();
var func = function.GetType().IsActionDelegate();
}
}
Single and dueces are true. func is false
These are distinct types with nothing in common but their name. The only semi-reasonable shortcut I can think of:
public static bool IsActionDelegate( this Type source )
{
return source.FullName.StartsWith("System.Action");
}
Certainly not fail-safe, but whomever declares his own types in the System namespace deserves some pain and suffering.