在 C# 中使用作为参数传递的类型的方法

Using methods from a type passed as a parameter in C#

我正在使用 Q#,这是一种基于 C# 的量子编程语言。量子操作变成了 C# classes,你可以从中做类似

的事情
QuantumOperation.run(simulator, param1, param2);

这将使用量子模拟器 simulator 来 运行 操作 QuantumOperation,参数 param1param2

我有很多不同的操作,我想 运行 使用不同的模拟器和不同的参数。我想做的是将量子操作传递给另一种方法,该方法将遍历所有模拟器和参数。然后我可以用我想要的所有量子操作调用这个方法。

问题是——据我所知——量子运算实际上是一个 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> 并采用 QCTraceSimulatorlong 作为参数的方法的问题。但是,此解决方案避免了必须修改任何 QuantumOperation 类(并希望能教您一些有关委托的知识)

下面是 paramsListRunMethodsOnSimulator 方法需要 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