C# 反射识别扩展方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/299515/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reflection to Identify Extension Methods
提问by Mike Chess
In C# is there a technique using reflection to determine if a method has been added to a class as an extension method?
在 C# 中是否有使用反射来确定方法是否已作为扩展方法添加到类中的技术?
Given an extension method such as the one shown below is it possible to determine that Reverse() has been added to the string class?
给定如下所示的扩展方法,是否可以确定已将 Reverse() 添加到字符串类中?
public static class StringExtensions
{
public static string Reverse(this string value)
{
char[] cArray = value.ToCharArray();
Array.Reverse(cArray);
return new string(cArray);
}
}
We're looking for a mechanism to determine in unit testing that the extension method was appropriately added by the developer. One reason to attempt this is that it is possible that a similar method would be added to the actual class by the developer and, if it was, the compiler will pick that method up.
我们正在寻找一种机制来在单元测试中确定扩展方法是由开发人员适当添加的。尝试这样做的一个原因是,开发人员可能会将类似的方法添加到实际类中,如果是这样,编译器会选择该方法。
采纳答案by Jon Skeet
You have to look in all the assemblies where the extension method may be defined.
您必须查看可以定义扩展方法的所有程序集。
Look for classes decorated with ExtensionAttribute
, and then methods within that class which are alsodecorated with ExtensionAttribute
. Then check the type of the first parameter to see if it matches the type you're interested in.
查找用 修饰的类ExtensionAttribute
,然后查找该类中也用修饰的方法ExtensionAttribute
。然后检查第一个参数的类型,看它是否与您感兴趣的类型匹配。
Here's some complete code. It could be more rigorous (it's not checking that the type isn't nested, or that there is at least one parameter) but it should give you a helping hand.
这是一些完整的代码。它可能更严格(它不检查类型是否嵌套,或者至少有一个参数),但它应该可以帮助您。
using System;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
public static class FirstExtensions
{
public static void Foo(this string x) {}
public static void Bar(string x) {} // Not an ext. method
public static void Baz(this int x) {} // Not on string
}
public static class SecondExtensions
{
public static void Quux(this string x) {}
}
public class Test
{
static void Main()
{
Assembly thisAssembly = typeof(Test).Assembly;
foreach (MethodInfo method in GetExtensionMethods(thisAssembly,
typeof(string)))
{
Console.WriteLine(method);
}
}
static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
Type extendedType)
{
var query = from type in assembly.GetTypes()
where type.IsSealed && !type.IsGenericType && !type.IsNested
from method in type.GetMethods(BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
where method.GetParameters()[0].ParameterType == extendedType
select method;
return query;
}
}
回答by James Curran
To clarify a point Jon glossed over... "Adding" an extension method to a class does not change the class in any way. It's just a little bit of spinning performed by the C# compiler.
为了澄清 Jon 掩盖的一点......“添加”一个扩展方法到一个类不会以任何方式改变这个类。这只是由 C# 编译器执行的一点点旋转。
So, using your example, you may write
所以,使用你的例子,你可以写
string rev = myStr.Reverse();
but the MSIL written to the assembly will be exactly as if you had written it:
但是写入程序集的 MSIL 将与您编写的完全一样:
string rev = StringExtensions.Reverse(myStr);
The compiler is merely letting you fool yourself into thinking you are calling an method of String.
编译器只是让您自欺欺人地认为您正在调用 String 的方法。
回答by Amy B
One reason to attempt this is that it is possible that a similar method would be added to the actual class by the developer and, if it was, the compiler will pick that method up.
尝试这样做的一个原因是,开发人员可能会将类似的方法添加到实际类中,如果是这样,编译器会选择该方法。
- Suppose an extension method void Foo(this Customer someCustomer)is defined.
- Suppose, also, that Customer is modified and the method void Foo()is added.
- Then, the new method on Customer will cover/hide the extension method.
- 假设定义了一个扩展方法void Foo(this Customer someCustomer)。
- 还假设修改了 Customer 并添加了void Foo()方法。
- 然后, Customer 上的新方法将覆盖/隐藏扩展方法。
The only way to call the old Foo method at that point is:
此时调用旧的 Foo 方法的唯一方法是:
CustomerExtension.Foo(myCustomer);
回答by drake7707
This will return a list of all extension methods defined in a certain type, including the generic ones:
这将返回在特定类型中定义的所有扩展方法的列表,包括通用方法:
public static IEnumerable<KeyValuePair<Type, MethodInfo>> GetExtensionMethodsDefinedInType(this Type t)
{
if (!t.IsSealed || t.IsGenericType || t.IsNested)
return Enumerable.Empty<KeyValuePair<Type, MethodInfo>>();
var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.IsDefined(typeof(ExtensionAttribute), false));
List<KeyValuePair<Type, MethodInfo>> pairs = new List<KeyValuePair<Type, MethodInfo>>();
foreach (var m in methods)
{
var parameters = m.GetParameters();
if (parameters.Length > 0)
{
if (parameters[0].ParameterType.IsGenericParameter)
{
if (m.ContainsGenericParameters)
{
var genericParameters = m.GetGenericArguments();
Type genericParam = genericParameters[parameters[0].ParameterType.GenericParameterPosition];
foreach (var constraint in genericParam.GetGenericParameterConstraints())
pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m));
}
}
else
pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m));
}
}
return pairs;
}
There's only one problem with this: The Type returned is not the same you'd expect with typeof(..), because it's a generic parameter type. In order to find all the extension methods for a given type you'll have to compare the GUID of all the base types and interfaces of the Type like:
这只有一个问题:返回的类型与您期望的 typeof(..) 不同,因为它是通用参数类型。为了找到给定类型的所有扩展方法,您必须比较 Type 的所有基本类型和接口的 GUID,例如:
public List<MethodInfo> GetExtensionMethodsOf(Type t)
{
List<MethodInfo> methods = new List<MethodInfo>();
Type cur = t;
while (cur != null)
{
TypeInfo tInfo;
if (typeInfo.TryGetValue(cur.GUID, out tInfo))
methods.AddRange(tInfo.ExtensionMethods);
foreach (var iface in cur.GetInterfaces())
{
if (typeInfo.TryGetValue(iface.GUID, out tInfo))
methods.AddRange(tInfo.ExtensionMethods);
}
cur = cur.BaseType;
}
return methods;
}
To be complete:
要完整:
I keep a dictionary of type info objects, that I build when iterating all the types of all assemblies:
我保留了一个类型信息对象的字典,这是我在迭代所有程序集的所有类型时构建的:
private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>();
where the TypeInfo
is defined as:
其中TypeInfo
定义为:
public class TypeInfo
{
public TypeInfo()
{
ExtensionMethods = new List<MethodInfo>();
}
public List<ConstructorInfo> Constructors { get; set; }
public List<FieldInfo> Fields { get; set; }
public List<PropertyInfo> Properties { get; set; }
public List<MethodInfo> Methods { get; set; }
public List<MethodInfo> ExtensionMethods { get; set; }
}
回答by Wolfgang Stelzhammer
Based on John Skeet's answer I've created my own extension to the System.Type-type.
根据 John Skeet 的回答,我创建了自己对 System.Type 类型的扩展。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace System
{
public static class TypeExtension
{
/// <summary>
/// This Methode extends the System.Type-type to get all extended methods. It searches hereby in all assemblies which are known by the current AppDomain.
/// </summary>
/// <remarks>
/// Insired by Jon Skeet from his answer on http://stackoverflow.com/questions/299515/c-sharp-reflection-to-identify-extension-methods
/// </remarks>
/// <returns>returns MethodInfo[] with the extended Method</returns>
public static MethodInfo[] GetExtensionMethods(this Type t)
{
List<Type> AssTypes = new List<Type>();
foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies())
{
AssTypes.AddRange(item.GetTypes());
}
var query = from type in AssTypes
where type.IsSealed && !type.IsGenericType && !type.IsNested
from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
where method.GetParameters()[0].ParameterType == t
select method;
return query.ToArray<MethodInfo>();
}
/// <summary>
/// Extends the System.Type-type to search for a given extended MethodeName.
/// </summary>
/// <param name="MethodeName">Name of the Methode</param>
/// <returns>the found Methode or null</returns>
public static MethodInfo GetExtensionMethod(this Type t, string MethodeName)
{
var mi = from methode in t.GetExtensionMethods()
where methode.Name == MethodeName
select methode;
if (mi.Count<MethodInfo>() <= 0)
return null;
else
return mi.First<MethodInfo>();
}
}
}
It get's all assemblies from the current AppDomain and searches for extended methods.
它从当前 AppDomain 获取所有程序集并搜索扩展方法。
Usage:
用法:
Type t = typeof(Type);
MethodInfo[] extendedMethods = t.GetExtensionMethods();
MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");
The next step would be to extend System.Type with methods, which returns all Methods (also the "normal" ones with the extended ones)
下一步是使用方法扩展 System.Type,它返回所有方法(还有带有扩展方法的“普通”方法)
回答by billy
void Main()
{
var test = new Test();
var testWithMethod = new TestWithExtensionMethod();
Tools.IsExtensionMethodCall(() => test.Method()).Dump();
Tools.IsExtensionMethodCall(() => testWithMethod.Method()).Dump();
}
public class Test
{
public void Method() { }
}
public class TestWithExtensionMethod
{
}
public static class Extensions
{
public static void Method(this TestWithExtensionMethod test) { }
}
public static class Tools
{
public static MethodInfo GetCalledMethodInfo(Expression<Action> expr)
{
var methodCall = expr.Body as MethodCallExpression;
return methodCall.Method;
}
public static bool IsExtensionMethodCall(Expression<Action> expr)
{
var methodInfo = GetCalledMethodInfo(expr);
return methodInfo.IsStatic;
}
}
Outputs:
输出:
False
错误的
True
真的