反思:如何在 C# 7.0 中查找和调用本地函数?
Reflection: How do I find and invoke a local functon in C# 7.0?
我有一个私有静态泛型方法,我想使用反射调用它,但我真的想 'bundle' 它在另一个方法中。 C# 7.0 支持本地函数,所以这绝对是可能的。
你会说 "why don't you just call it directly?" 但我正在使用它来获得以强类型方式使用对象和 System.Type 的能力,因此我需要动态调用它。如果我拥有这段代码,它就已经可以工作了,因为它是自己的私有静态泛型方法。
private static void HandleResponse(object data, Type asType)
{
var application = typeof(Program);
application
.GetMethod(nameof(UseAs), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(asType)
.Invoke(null, new object[] { data });
}
public static void UseAs<T>(T obj)
{
Console.WriteLine($"Object is now a: {typeof(T)}:");
};
以上代码有效。如果我传入:
data: new TestObject(),
type: typeof(TestObject)
我实际上在 UseAs 中有一个 TestObject。
所以,我想把这一切放在一个方法中,就像这样:
private static void HandleResponse(object data, Type asType)
{
void useAs<T>(T obj)
{
Console.WriteLine($"Object is now a: {typeof(T)}:");
};
var application = typeof(Program);
application
.GetMethod(nameof(UseAs), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(asType)
.Invoke(null, new object[] { data });
}
不幸的是,GetMethod 代码不再有效。我听说在编译时,编译器会将任何本地函数转换为静态方法,所以我直接跳到 window 和 运行:
application.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
...而且,我确实看到了这个回复:
{System.Reflection.MethodInfo[3]}
[0]: {Void Main(System.String[])}
[1]: {Void HandleResponse(System.Object, System.Type)}
[2]: {Void <HandleResponse>g__useAs1_0[T](T)}
这是列表中的最后一种方法。有谁知道您将如何以合理的方式访问这样的方法?
谢谢!
编辑:
我确实可以把UseAs当作一个普通的私有静态方法来使用。它只是不会在其他任何地方使用,所以我想 "package" 在一个方法中全部完成。
此外,这实际上应该是一个关于一般查找局部函数的问题,而且在 Whosebug 上的其他任何地方似乎都没有关于它的问题。我很难相信在某些时候,至少有人不会对此感到好奇。
一开始我对提供任何代码犹豫不决,因为我只是在修补一个想法,但我试图实现的实际目标完全次于这个问题。
用反射调用局部函数就像是在找麻烦。该名称不是 "fixed"。它根据相同的其他本地函数的数量而变化 class... 因此,如果您修改另一个方法,您可以更改您感兴趣的本地函数的名称。
你可以看看这个TryRoslyn。
共有三个 class,Class1
、Class2
和 Class3
。它们都有一个方法M
,内部有一个局部函数Test
。 Class1
和 Class2
与最后一个字符相同。然后本地方法被编译为名为 <M>g__Test0_0()
的方法。 Class3
在 M
方法之前引入了另一种方法,Filler
,带有另一个局部函数 (Foo
),然后编译为 <Filler>g__Foo0_0
。在这种情况下,M
的本地方法被命名为 <M>g__Test1_0()
.
好的,我有办法了。但它确实可怕。它涉及使用 specific 类型从您的方法创建委托,然后使用它来查找 generic 方法,然后构造另一个特定方法并调用它.
所以我们从 UseAs<int>
到 UseAs<T>
再到 UseAs<the-type-we-want>
。
它在很多方面都可能出错,但它适用于我测试过的非常有限的样本:
// DISCLAIMER: THIS CODE IS FAIRLY HACKY, AND MAY WELL FAIL IN WEIRD
// SITUATIONS. USE WITH EXTREME CAUTION AND LOTS OF TESTS!
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
HandleResponse("foo", typeof(string));
}
static void HandleResponse(object data, Type type)
{
string local = "This was a local variable";
void UseAs<T>(T obj)
{
Console.WriteLine($"Object is now a: {typeof(T)}:");
// Proof that we're capturing the target too
Console.WriteLine($"Local was {local}");
}
InvokeHelper(UseAs, data, type);
}
// This could be in any class you want
static void InvokeHelper(Action<int> int32Action, object data, Type type)
{
// You probably want to validate that it really is a generic method...
var method = int32Action.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(new[] { type });
concreteMethod.Invoke(int32Action.Target, new[] { data });
}
}
我知道这是一个老问题了 - 但我正在尝试理解为什么 HandleResponse 本身不能成为通用方法?您可以将该类型传递给本地函数,该函数本身可以使用相同的外部类型。这消除了反射的需要。这意味着每次使用不同类型的调用都会生成新方法 - 但我不知道如何避免编译和执行代码中的该步骤 - 你的 UseAs 是通用的。所以这就是我的想法。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class testy
{
class a { }
class b : a { }
[TestMethod]
public void caller()
{
HandleResponse<a>(new b());
}
private static void HandleResponse<T>(object data)
{
UseAs((T)data);
void UseAs(T obj)
{
System.Diagnostics.Debug.WriteLine($"Object is now a: {typeof(T)}:");
}
}
}
我假设您实际上是在尝试将一种类型转换为另一种有效类型。在这种情况下,您的代码将不是类型安全的,并且可能会失败并出现类似“obj 不可转换为类型 DateTime”的错误。我的解决方案也不例外。我假设您可以将您的对象转换为 T。我不检查它是否可以转换为该类型(在这种情况下有效使用反射)。所以我相信我的小解决方案在功能上是等效的。
我有一个私有静态泛型方法,我想使用反射调用它,但我真的想 'bundle' 它在另一个方法中。 C# 7.0 支持本地函数,所以这绝对是可能的。
你会说 "why don't you just call it directly?" 但我正在使用它来获得以强类型方式使用对象和 System.Type 的能力,因此我需要动态调用它。如果我拥有这段代码,它就已经可以工作了,因为它是自己的私有静态泛型方法。
private static void HandleResponse(object data, Type asType)
{
var application = typeof(Program);
application
.GetMethod(nameof(UseAs), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(asType)
.Invoke(null, new object[] { data });
}
public static void UseAs<T>(T obj)
{
Console.WriteLine($"Object is now a: {typeof(T)}:");
};
以上代码有效。如果我传入:
data: new TestObject(),
type: typeof(TestObject)
我实际上在 UseAs 中有一个 TestObject。
所以,我想把这一切放在一个方法中,就像这样:
private static void HandleResponse(object data, Type asType)
{
void useAs<T>(T obj)
{
Console.WriteLine($"Object is now a: {typeof(T)}:");
};
var application = typeof(Program);
application
.GetMethod(nameof(UseAs), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(asType)
.Invoke(null, new object[] { data });
}
不幸的是,GetMethod 代码不再有效。我听说在编译时,编译器会将任何本地函数转换为静态方法,所以我直接跳到 window 和 运行:
application.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
...而且,我确实看到了这个回复:
{System.Reflection.MethodInfo[3]}
[0]: {Void Main(System.String[])}
[1]: {Void HandleResponse(System.Object, System.Type)}
[2]: {Void <HandleResponse>g__useAs1_0[T](T)}
这是列表中的最后一种方法。有谁知道您将如何以合理的方式访问这样的方法?
谢谢!
编辑:
我确实可以把UseAs当作一个普通的私有静态方法来使用。它只是不会在其他任何地方使用,所以我想 "package" 在一个方法中全部完成。
此外,这实际上应该是一个关于一般查找局部函数的问题,而且在 Whosebug 上的其他任何地方似乎都没有关于它的问题。我很难相信在某些时候,至少有人不会对此感到好奇。
一开始我对提供任何代码犹豫不决,因为我只是在修补一个想法,但我试图实现的实际目标完全次于这个问题。
用反射调用局部函数就像是在找麻烦。该名称不是 "fixed"。它根据相同的其他本地函数的数量而变化 class... 因此,如果您修改另一个方法,您可以更改您感兴趣的本地函数的名称。
你可以看看这个TryRoslyn。
共有三个 class,Class1
、Class2
和 Class3
。它们都有一个方法M
,内部有一个局部函数Test
。 Class1
和 Class2
与最后一个字符相同。然后本地方法被编译为名为 <M>g__Test0_0()
的方法。 Class3
在 M
方法之前引入了另一种方法,Filler
,带有另一个局部函数 (Foo
),然后编译为 <Filler>g__Foo0_0
。在这种情况下,M
的本地方法被命名为 <M>g__Test1_0()
.
好的,我有办法了。但它确实可怕。它涉及使用 specific 类型从您的方法创建委托,然后使用它来查找 generic 方法,然后构造另一个特定方法并调用它.
所以我们从 UseAs<int>
到 UseAs<T>
再到 UseAs<the-type-we-want>
。
它在很多方面都可能出错,但它适用于我测试过的非常有限的样本:
// DISCLAIMER: THIS CODE IS FAIRLY HACKY, AND MAY WELL FAIL IN WEIRD
// SITUATIONS. USE WITH EXTREME CAUTION AND LOTS OF TESTS!
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
HandleResponse("foo", typeof(string));
}
static void HandleResponse(object data, Type type)
{
string local = "This was a local variable";
void UseAs<T>(T obj)
{
Console.WriteLine($"Object is now a: {typeof(T)}:");
// Proof that we're capturing the target too
Console.WriteLine($"Local was {local}");
}
InvokeHelper(UseAs, data, type);
}
// This could be in any class you want
static void InvokeHelper(Action<int> int32Action, object data, Type type)
{
// You probably want to validate that it really is a generic method...
var method = int32Action.Method;
var genericMethod = method.GetGenericMethodDefinition();
var concreteMethod = genericMethod.MakeGenericMethod(new[] { type });
concreteMethod.Invoke(int32Action.Target, new[] { data });
}
}
我知道这是一个老问题了 - 但我正在尝试理解为什么 HandleResponse 本身不能成为通用方法?您可以将该类型传递给本地函数,该函数本身可以使用相同的外部类型。这消除了反射的需要。这意味着每次使用不同类型的调用都会生成新方法 - 但我不知道如何避免编译和执行代码中的该步骤 - 你的 UseAs 是通用的。所以这就是我的想法。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class testy
{
class a { }
class b : a { }
[TestMethod]
public void caller()
{
HandleResponse<a>(new b());
}
private static void HandleResponse<T>(object data)
{
UseAs((T)data);
void UseAs(T obj)
{
System.Diagnostics.Debug.WriteLine($"Object is now a: {typeof(T)}:");
}
}
}
我假设您实际上是在尝试将一种类型转换为另一种有效类型。在这种情况下,您的代码将不是类型安全的,并且可能会失败并出现类似“obj 不可转换为类型 DateTime”的错误。我的解决方案也不例外。我假设您可以将您的对象转换为 T。我不检查它是否可以转换为该类型(在这种情况下有效使用反射)。所以我相信我的小解决方案在功能上是等效的。