在 C# 中使用作为参数传递的类型的方法
Using methods from a type passed as a parameter in C#
我正在使用 Q#,这是一种基于 C# 的量子编程语言。量子操作变成了 C# classes,你可以从中做类似
的事情
QuantumOperation.run(simulator, param1, param2);
这将使用量子模拟器 simulator
来 运行 操作 QuantumOperation
,参数 param1
和 param2
。
我有很多不同的操作,我想 运行 使用不同的模拟器和不同的参数。我想做的是将量子操作传递给另一种方法,该方法将遍历所有模拟器和参数。然后我可以用我想要的所有量子操作调用这个方法。
问题是——据我所知——量子运算实际上是一个 class 而不是一个对象。因此,例如,如果我写:
static void someMethod<Qop>(){...}
然后我可以用量子运算 QuantumOperation
调用它:
someMethod<QuantumOperation>()
并且编译正常。但是,如果我尝试做类似
static void someMethod<Qop>(Qop quantumOperation){ ...}
someMethod<QuantumOperation>(quantumOperation);
第二行出现错误“QuantumOperation
是一种类型,在给定上下文中无效”。
如果我尝试:
static void someMethod<Qop>(...){
...
Qop.Run(...);
...
}
它类似地表示:“'Qop' 是一个类型参数,在给定的上下文中无效”。
这里似乎发生的是我将 class 作为类型传递。但是,当我想将该类型视为 class 时,我做不到。我寻找将 class 作为参数传递的方法,但我只看到在 class 中创建对象的方法。但是我不能使用对象,因为 "Run" 是一个静态方法。
(我可以尝试传递一个对象并从中获取 class,但是 (a) 我不知道是否有可能创建量子操作对象 classes,并且 ( b) 我只能找到 public Type GetType
,returns 类型而不是 class,给出同样的问题)。
有没有办法将 class 作为参数传递,然后引用 class 的静态方法,而无需实例化对象?
现在,也许我要求太多了,因为就 C# 而言,所有这些 classes 都有一个名为 "Run" 的方法是巧合。也许不应该 能够尝试从不同的class 调用具有相同名称的方法。
或者,我可以为每个量子操作构造一个方法,然后传递这些方法。该方法看起来像:
static void QuantumOperationWrapper(QuantumSimulator simulator, Int int_parameter){
QuantumOperation.Run(simulator, in_parameter);
}
我需要为每个量子操作创建一个新方法,但这还不错。然后我可以将其作为委托或 Func 传递给我想要的方法。问题是我想要的结果包含在 QuantumSimulator
对象中。所以我想做的是:
QuantumOperationWrapper(simulator, 3);
simulator.GetResults();
但是当我这样做时,结果是空的。我的猜测是,不知何故,模拟器正在按值传递,或者被视为不可变的,或者阻止 QuantumOperationWrapper
改变模拟器内部参数的东西。
有什么方法可以确保 delegate/Func 会改变其参数的内部状态?
编辑:我可以为 Run
方法创建一个委托,如下所示:
public delegate System.Threading.Tasks.Task<Microsoft.Quantum.Simulation.Core.QVoid> RunQop(QCTraceSimulator sim, long n);
然后我可以构造static void someMethod(RunQop runner, ...)
,并将QuantumOperation.Run
作为第一个参数传递。
但是,我有同样的问题,我作为参数传递的 QCTraceSimulator
不保留我调用它时产生的任何模拟结果。
也许您可以尝试使用接口来处理这种情况。类似的东西:
public interface IQuantumOperation
{
void Run();
void Run(MyFancyClazz simulator, MyFancyParam param1, MyFancyParam param2);
//And other possible methods
}
然后你可以使用这个接口作为类型参数的契约
static void someMethod<Qop>(Qop myQopParameter) where Qop : IQuantumOperation
{
...
//Now you can call your Run method
myQopParameter.Run(...);
...
//Or other fancy Run method with parameters like below
myQopParameter.Run(simulator, param1, param2);
}
最后确保您的 QuantumOperation class 实现了 IQuantumOperation 接口
我不是很了解所有内容,但据我所知,您可以使用非静态包装器,并且每个包装器都允许访问不同的 Qop 静态 class。
static public void TestQop()
{
someMethod(new Qop1(), 0, 0, 0);
someMethod(new Qop2(), 1, 1, 1);
}
static void someMethod<T>(T qop, int simulator, int param1, int param2)
where T : QopBase
{
qop.Run(simulator, param1, param2);
}
abstract class QopBase
{
public abstract void Run(int simulator, int param1, int param2);
}
class Qop1 : QopBase
{
public override void Run(int simulator, int param1, int param2)
{
QuantumOperation1.Run(simulator, param1, param2);
}
}
class Qop2 : QopBase
{
public override void Run(int simulator, int param1, int param2)
{
QuantumOperation2.Run(simulator, param1, param2);
}
}
因此,如果我理解正确的话,您想在不同的模拟器上执行一堆带有参数的方法。以下是如何做到这一点:
我们首先需要一个我们要执行的操作的列表。
var methodList = new List<Func<QCTraceSimulator, long, Task<QVoid>>>
{
QuantumOperation.Run,
// Add more methods here
}
这是 Func 的列表。 Func 是一种委托类型,表示具有参数和 return 值的方法。这里我们的方法需要看起来像这样才能添加到我们的列表中:
public Task<QVoid> SomeName(QCTraceSimulator sim, long parameter)
{ ...}
我们还需要您要尝试使用的参数列表:
var paramsList = new List<long>
{
1,
2,
-2147483648,
2147483647
};
现在我们可以像这样迭代这些和运行我们的方法:
public void RunMethodsOnSimulator(QCTraceSimulator sim)
{
// Iterate through every method
foreach (var method in methodList)
{
// Iterate through every parameter
foreach (var parameter in paramsList)
{
// Execute the given method with the given parameter
Task<QVoid> result = method(sim, parameter);
}
}
}
您现在可以使用 result
做任何您想做的事。这将导致使用每个参数调用每个方法一次
请记住,此答案仅解决了 return a Task<QVoid>
并采用 QCTraceSimulator
和 long
作为参数的方法的问题。但是,此解决方案避免了必须修改任何 QuantumOperation
类(并希望能教您一些有关委托的知识)
下面是 paramsList
和 RunMethodsOnSimulator
方法需要 2 个或更多参数的情况:
methodList = new List<Func<QCTraceSimulator, long, int, Task<QVoid>>>
{
QuantumOperation.Run,
// Add more methods here
}
paramsList = new List<Tuple<long, int>>
{
new Tuple<long, int>(1, 1),
new Tuple<long, int>(2, 1),
new Tuple<long, int>(1, 2),
new Tuple<long, int>(-2147483648, 1)
}
public void RunMethodsOnSimulator(QCTraceSimulator sim)
{
// Iterate through every method
foreach (var method in methodList)
{
// Iterate through every parameter
foreach (var parameter in paramsList)
{
// Execute the given method with the given parameter
Task<QVoid> result = method(sim, parameter.Item1, parameter.Item2);
}
}
}
在其类型被通用定义的对象上调用方法需要您使用 generic constraint 以确保所使用的通用类型定义了预期的方法。
在其核心,这依赖于多态性来确保即使特定类型可能会有所不同,但已知所有可用的泛型类型(可以通过约束进行限制)都包含您希望调用的特定方法。
静态 类 和方法缺少此功能。它们不能继承,也不能实现接口,你也不能通过方法参数传递它们(并且尝试通过泛型来实现不是解决方案)。没有办法在两个不同的static类的两个static方法之间创建一个"inheritance-like"link;即使这些方法在其他方面具有相同的签名。
还有其他方法吗?是的。优先顺序:
(1) 直接而干净的解决方案是 避免静态 而是使用实例化 类。如果你能做到这一点,这是更好的选择。
(2) 如果您无法避免静态,您仍然可以将静态包装在实例化包装器中,例如:
public class IWrapper
{
void DoTheThing(int foo);
}
public QuantumOperationWrapper : IWrapper
{
public void DoTheThing(int foo)
{
QuantumOperationWrapper.Run(foo);
}
}
public OtherStaticOperationWrapper : IWrapper
{
public void DoTheThing(int foo)
{
OtherStaticOperationWrapper.Run(foo);
}
}
这实际上是 "unstatics" 静态代码,在某种程度上,您现在可以依靠所有包装器 implement/inherit 通用 BaseWrapper
的知识,因此都实现了 DoTheThing
方法。
您的通用方法可以依赖于此:
public void DoTheGenericThing<T>(T obj) where T : IWrapper
{
obj.DoTheThing(123);
}
注意:在这种特殊情况下,您甚至不需要一开始就使用泛型。我假设在这种情况下您真的不需要泛型,但由于答案可以同时适用于泛型和非泛型情况,所以我在解决方案中保留了泛型参数。可能在某些特定情况下您仍然需要使用泛型,但我怀疑这不是其中之一。
(3) 第三个但 非常脏 选项是使用反射来调用该方法,只是假设你永远不会传入没有预期的静态方法的类型。但这是一种非常糟糕的实践方法,它会充满错误,几乎不可能调试,而且绝对不利于重构。
Q# 模拟测试处理此问题的方法是使用一种方法接收委托,其中包含您要在模拟器上执行的一些代码,特别是模拟器单元测试具有 RunWithMultipleSimulators
method that is broadly used in places like CoreTests.cs;这是一个如何使用它的例子:
[Fact]
public void RandomOperation()
{
Helper.RunWithMultipleSimulators((s) =>
{
Circuits.RandomOperationTest.Run(s).Wait(); // Throws if it doesn't succeed
});
}
我认为您遇到了两个不同的问题:您没有得到结果,并且处理 classes 使得循环执行不同的操作变得困难。让我尝试分别解决它们。
来自 运行 操作的结果是 return 从 Run
方法编辑的,而不是存储在模拟器中。更具体地说,如果调用 return 为 Q# int
的操作,Run
方法的 return 值将为 Task<long>
。然后,您可以使用任务的 value
属性 来获取实际结果,或者使用 async
/await
模式,随您喜欢。
所有的操作classes都可以实例化,它们都实现了ICallable
接口。该接口有一个 Apply
方法,该方法将参数传递给操作并 return 获取(异步)结果。每个实例都必须通过对模拟器的引用来正确实例化;最简单的方法是在模拟器实例上调用 Get
泛型方法。
如果你看一下SimulatorBase.cs,在第101行Run
方法的实现中,你可以看到这是怎么做到的。在这个方法中,T
是操作的class; I
是操作输入的class; O
是操作 return 值的 class。您可以使用基本相同的代码创建对象列表,然后使用不同的参数调用 Apply
。
我正在使用 Q#,这是一种基于 C# 的量子编程语言。量子操作变成了 C# classes,你可以从中做类似
的事情QuantumOperation.run(simulator, param1, param2);
这将使用量子模拟器 simulator
来 运行 操作 QuantumOperation
,参数 param1
和 param2
。
我有很多不同的操作,我想 运行 使用不同的模拟器和不同的参数。我想做的是将量子操作传递给另一种方法,该方法将遍历所有模拟器和参数。然后我可以用我想要的所有量子操作调用这个方法。
问题是——据我所知——量子运算实际上是一个 class 而不是一个对象。因此,例如,如果我写:
static void someMethod<Qop>(){...}
然后我可以用量子运算 QuantumOperation
调用它:
someMethod<QuantumOperation>()
并且编译正常。但是,如果我尝试做类似
static void someMethod<Qop>(Qop quantumOperation){ ...}
someMethod<QuantumOperation>(quantumOperation);
第二行出现错误“QuantumOperation
是一种类型,在给定上下文中无效”。
如果我尝试:
static void someMethod<Qop>(...){
...
Qop.Run(...);
...
}
它类似地表示:“'Qop' 是一个类型参数,在给定的上下文中无效”。
这里似乎发生的是我将 class 作为类型传递。但是,当我想将该类型视为 class 时,我做不到。我寻找将 class 作为参数传递的方法,但我只看到在 class 中创建对象的方法。但是我不能使用对象,因为 "Run" 是一个静态方法。
(我可以尝试传递一个对象并从中获取 class,但是 (a) 我不知道是否有可能创建量子操作对象 classes,并且 ( b) 我只能找到 public Type GetType
,returns 类型而不是 class,给出同样的问题)。
有没有办法将 class 作为参数传递,然后引用 class 的静态方法,而无需实例化对象?
现在,也许我要求太多了,因为就 C# 而言,所有这些 classes 都有一个名为 "Run" 的方法是巧合。也许不应该 能够尝试从不同的class 调用具有相同名称的方法。
或者,我可以为每个量子操作构造一个方法,然后传递这些方法。该方法看起来像:
static void QuantumOperationWrapper(QuantumSimulator simulator, Int int_parameter){
QuantumOperation.Run(simulator, in_parameter);
}
我需要为每个量子操作创建一个新方法,但这还不错。然后我可以将其作为委托或 Func 传递给我想要的方法。问题是我想要的结果包含在 QuantumSimulator
对象中。所以我想做的是:
QuantumOperationWrapper(simulator, 3);
simulator.GetResults();
但是当我这样做时,结果是空的。我的猜测是,不知何故,模拟器正在按值传递,或者被视为不可变的,或者阻止 QuantumOperationWrapper
改变模拟器内部参数的东西。
有什么方法可以确保 delegate/Func 会改变其参数的内部状态?
编辑:我可以为 Run
方法创建一个委托,如下所示:
public delegate System.Threading.Tasks.Task<Microsoft.Quantum.Simulation.Core.QVoid> RunQop(QCTraceSimulator sim, long n);
然后我可以构造static void someMethod(RunQop runner, ...)
,并将QuantumOperation.Run
作为第一个参数传递。
但是,我有同样的问题,我作为参数传递的 QCTraceSimulator
不保留我调用它时产生的任何模拟结果。
也许您可以尝试使用接口来处理这种情况。类似的东西:
public interface IQuantumOperation
{
void Run();
void Run(MyFancyClazz simulator, MyFancyParam param1, MyFancyParam param2);
//And other possible methods
}
然后你可以使用这个接口作为类型参数的契约
static void someMethod<Qop>(Qop myQopParameter) where Qop : IQuantumOperation
{
...
//Now you can call your Run method
myQopParameter.Run(...);
...
//Or other fancy Run method with parameters like below
myQopParameter.Run(simulator, param1, param2);
}
最后确保您的 QuantumOperation class 实现了 IQuantumOperation 接口
我不是很了解所有内容,但据我所知,您可以使用非静态包装器,并且每个包装器都允许访问不同的 Qop 静态 class。
static public void TestQop()
{
someMethod(new Qop1(), 0, 0, 0);
someMethod(new Qop2(), 1, 1, 1);
}
static void someMethod<T>(T qop, int simulator, int param1, int param2)
where T : QopBase
{
qop.Run(simulator, param1, param2);
}
abstract class QopBase
{
public abstract void Run(int simulator, int param1, int param2);
}
class Qop1 : QopBase
{
public override void Run(int simulator, int param1, int param2)
{
QuantumOperation1.Run(simulator, param1, param2);
}
}
class Qop2 : QopBase
{
public override void Run(int simulator, int param1, int param2)
{
QuantumOperation2.Run(simulator, param1, param2);
}
}
因此,如果我理解正确的话,您想在不同的模拟器上执行一堆带有参数的方法。以下是如何做到这一点:
我们首先需要一个我们要执行的操作的列表。
var methodList = new List<Func<QCTraceSimulator, long, Task<QVoid>>>
{
QuantumOperation.Run,
// Add more methods here
}
这是 Func 的列表。 Func 是一种委托类型,表示具有参数和 return 值的方法。这里我们的方法需要看起来像这样才能添加到我们的列表中:
public Task<QVoid> SomeName(QCTraceSimulator sim, long parameter)
{ ...}
我们还需要您要尝试使用的参数列表:
var paramsList = new List<long>
{
1,
2,
-2147483648,
2147483647
};
现在我们可以像这样迭代这些和运行我们的方法:
public void RunMethodsOnSimulator(QCTraceSimulator sim)
{
// Iterate through every method
foreach (var method in methodList)
{
// Iterate through every parameter
foreach (var parameter in paramsList)
{
// Execute the given method with the given parameter
Task<QVoid> result = method(sim, parameter);
}
}
}
您现在可以使用 result
做任何您想做的事。这将导致使用每个参数调用每个方法一次
请记住,此答案仅解决了 return a Task<QVoid>
并采用 QCTraceSimulator
和 long
作为参数的方法的问题。但是,此解决方案避免了必须修改任何 QuantumOperation
类(并希望能教您一些有关委托的知识)
下面是 paramsList
和 RunMethodsOnSimulator
方法需要 2 个或更多参数的情况:
methodList = new List<Func<QCTraceSimulator, long, int, Task<QVoid>>>
{
QuantumOperation.Run,
// Add more methods here
}
paramsList = new List<Tuple<long, int>>
{
new Tuple<long, int>(1, 1),
new Tuple<long, int>(2, 1),
new Tuple<long, int>(1, 2),
new Tuple<long, int>(-2147483648, 1)
}
public void RunMethodsOnSimulator(QCTraceSimulator sim)
{
// Iterate through every method
foreach (var method in methodList)
{
// Iterate through every parameter
foreach (var parameter in paramsList)
{
// Execute the given method with the given parameter
Task<QVoid> result = method(sim, parameter.Item1, parameter.Item2);
}
}
}
在其类型被通用定义的对象上调用方法需要您使用 generic constraint 以确保所使用的通用类型定义了预期的方法。
在其核心,这依赖于多态性来确保即使特定类型可能会有所不同,但已知所有可用的泛型类型(可以通过约束进行限制)都包含您希望调用的特定方法。
静态 类 和方法缺少此功能。它们不能继承,也不能实现接口,你也不能通过方法参数传递它们(并且尝试通过泛型来实现不是解决方案)。没有办法在两个不同的static类的两个static方法之间创建一个"inheritance-like"link;即使这些方法在其他方面具有相同的签名。
还有其他方法吗?是的。优先顺序:
(1) 直接而干净的解决方案是 避免静态 而是使用实例化 类。如果你能做到这一点,这是更好的选择。
(2) 如果您无法避免静态,您仍然可以将静态包装在实例化包装器中,例如:
public class IWrapper
{
void DoTheThing(int foo);
}
public QuantumOperationWrapper : IWrapper
{
public void DoTheThing(int foo)
{
QuantumOperationWrapper.Run(foo);
}
}
public OtherStaticOperationWrapper : IWrapper
{
public void DoTheThing(int foo)
{
OtherStaticOperationWrapper.Run(foo);
}
}
这实际上是 "unstatics" 静态代码,在某种程度上,您现在可以依靠所有包装器 implement/inherit 通用 BaseWrapper
的知识,因此都实现了 DoTheThing
方法。
您的通用方法可以依赖于此:
public void DoTheGenericThing<T>(T obj) where T : IWrapper
{
obj.DoTheThing(123);
}
注意:在这种特殊情况下,您甚至不需要一开始就使用泛型。我假设在这种情况下您真的不需要泛型,但由于答案可以同时适用于泛型和非泛型情况,所以我在解决方案中保留了泛型参数。可能在某些特定情况下您仍然需要使用泛型,但我怀疑这不是其中之一。
(3) 第三个但 非常脏 选项是使用反射来调用该方法,只是假设你永远不会传入没有预期的静态方法的类型。但这是一种非常糟糕的实践方法,它会充满错误,几乎不可能调试,而且绝对不利于重构。
Q# 模拟测试处理此问题的方法是使用一种方法接收委托,其中包含您要在模拟器上执行的一些代码,特别是模拟器单元测试具有 RunWithMultipleSimulators
method that is broadly used in places like CoreTests.cs;这是一个如何使用它的例子:
[Fact]
public void RandomOperation()
{
Helper.RunWithMultipleSimulators((s) =>
{
Circuits.RandomOperationTest.Run(s).Wait(); // Throws if it doesn't succeed
});
}
我认为您遇到了两个不同的问题:您没有得到结果,并且处理 classes 使得循环执行不同的操作变得困难。让我尝试分别解决它们。
来自 运行 操作的结果是 return 从 Run
方法编辑的,而不是存储在模拟器中。更具体地说,如果调用 return 为 Q# int
的操作,Run
方法的 return 值将为 Task<long>
。然后,您可以使用任务的 value
属性 来获取实际结果,或者使用 async
/await
模式,随您喜欢。
所有的操作classes都可以实例化,它们都实现了ICallable
接口。该接口有一个 Apply
方法,该方法将参数传递给操作并 return 获取(异步)结果。每个实例都必须通过对模拟器的引用来正确实例化;最简单的方法是在模拟器实例上调用 Get
泛型方法。
如果你看一下SimulatorBase.cs,在第101行Run
方法的实现中,你可以看到这是怎么做到的。在这个方法中,T
是操作的class; I
是操作输入的class; O
是操作 return 值的 class。您可以使用基本相同的代码创建对象列表,然后使用不同的参数调用 Apply
。