【问题标题】:Using methods from a type passed as a parameter in C#在 C# 中使用作为参数传递的类型中的方法
【发布时间】:2019-10-02 11:39:23
【问题描述】:

我正在使用基于 C# 的量子编程语言 Q#。量子操作变成了 C# 类,你可以从中做一些事情,比如

QuantumOperation.run(simulator, param1, param2);

它将使用量子模拟器 simulator 运行带有参数 param1param2 的操作 QuantumOperation

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

问题在于——据我所知——量子运算实际上是一个,而不是一个对象。所以,例如,如果我写:

static void someMethod<Qop>(){...}

然后我可以使用量子操作QuantumOperation 将其称为:

someMethod<QuantumOperation>()

它编译得很好。但是,如果我尝试做类似

static void someMethod<Qop>(Qop quantumOperation){ ...}

someMethod<QuantumOperation>(quantumOperation);

第二行出现“QuantumOperation 是一种类型,在给定上下文中无效”的错误。

如果我尝试:

static void someMethod<Qop>(...){
    ...
    Qop.Run(...);
    ...
}

它类似地表示:“'Qop' 是一个类型参数,在给定的上下文中无效”。

这里似乎发生的事情是我将类作为一种类型传递。但是当我想将类型视为一个类时,我不能。我寻找将类作为参数传递的方法,但我只看到了在该类中创建对象的方法。但我不能使用对象,因为“运行”是一种静态方法。

(我可以尝试传递一个对象并从中获取类,但是(a)我不知道是否可以创建量子操作类的对象,并且(b)我只能找到public Type GetType,返回一个类型而不是一个类,给出同样的问题)。

有没有办法将一个作为参数传递,然后引用该类的静态方法,而无需实例化对象?

现在,也许我问的太多了,因为就 C# 而言,所有这些类都有一个名为“Run”的方法是一个巧合。它可能不应该能够尝试从不同的类中调用具有相同名称的方法。

或者,我可以为每个量子操作构造一个方法,然后传递这些方法。该方法如下所示:

static void QuantumOperationWrapper(QuantumSimulator simulator, Int int_parameter){
    QuantumOperation.Run(simulator, in_parameter);
}

我需要为每个量子操作创建一个新方法,但这还不错。然后我可以将它作为委托或 Func 传递给我想要的方法。问题是我想要的结果包含在QuantumSimulator 对象中。所以我想做的是:

QuantumOperationWrapper(simulator, 3);
simulator.GetResults();

但是当我这样做时,结果是空的。我的猜测是,不知何故,模拟器是按值传递的,或者被视为不可变的,或者阻止QuantumOperationWrapper 改变模拟器的内部参数的东西。

有什么方法可以确保委托/Func 会改变其参数的内部状态?

编辑:我可以为 Run 方法创建一个委托,如下所示:

public delegate System.Threading.Tasks.Task&lt;Microsoft.Quantum.Simulation.Core.QVoid&gt; RunQop(QCTraceSimulator sim, long n);

然后我可以构造static void someMethod(RunQop runner, ...),并将QuantumOperation.Run 作为第一个参数传递。

但是,我有同样的问题,我作为参数传递的 QCTraceSimulator 没有保留它在调用它时所做的任何模拟结果。

【问题讨论】:

  • 在 C# 中有这些东西称为“委托”。每个方法都是一个委托,(更多阅读here,您可以使用委托作为参数,允许您将方法作为参数传递给方法。如果您可以发布QuantumOperation.run() 的方法签名,我可以给您一个委托来传递它作为参数
  • 如何找到方法签名?
  • 方法签名表示方法的名称、参数、返回类型和访问修饰符。要创建委托,您需要返回类型和参数。查看docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
  • 给定方法的方法签名是它被声明的地方。例如ToString方法的方法签名是public string ToString();
  • 好的,我想出了方法签名并尝试了它,但它没有存储结果(我编辑了问题以包含更多详细信息)

标签: c# types static-classes quantum-computing q#


【解决方案1】:

因此,如果我理解正确,您想在不同的模拟器上执行一堆带有参数的方法。以下是如何做到这一点:

我们首先需要一个我们想要执行的操作的列表。

var methodList = new List<Func<QCTraceSimulator, long, Task<QVoid>>>
{
    QuantumOperation.Run,
    // Add more methods here
}

这是Funcs 的列表。 Func 是一种委托类型,它表示具有参数和返回值的方法。这里我们的方法需要看起来像这样才能被添加到我们的列表中:

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 为所欲为。这将导致每个方法都被每个参数调用一次

请记住,此答案仅针对返回 Task&lt;QVoid&gt; 并以 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);
        }
    }
}

【讨论】:

  • 这(或更适合我的确切问题的非常类似的东西)有效,谢谢。
【解决方案2】:

Q# 模拟测试处理这个问题的方式是使用一个方法来接收一个委托,该委托带有一些你想在模拟器上执行的代码,特别是,模拟器单元测试有 RunWithMultipleSimulators 方法,该方法广泛用于像CoreTests.cs这样的地方;这是一个如何使用它的例子:

    [Fact]
    public void RandomOperation()
    {
        Helper.RunWithMultipleSimulators((s) =>
        {
            Circuits.RandomOperationTest.Run(s).Wait(); // Throws if it doesn't succeed
        });
    }

【讨论】:

  • 非常有趣,但我的目标是为跟踪模拟器提供不同的参数(例如,为不同的门设置不同的深度设置),然后还使用整数输入调用相同的操作作为量子比特数.
【解决方案3】:

我认为你有两个不同的问题:你没有得到结果,处理类使得循环通过不同的操作变得困难。让我试着分别解决它们。

运行操作的结果从Run 方法返回,而不是存储在模拟器中。更具体地说,如果您调用返回 Q# int 的操作,则 Run 方法的返回值将是 Task&lt;long&gt;。然后,您可以使用任务的value 属性来获取实际结果,或者使用async/await 模式,随心所欲。

所有的操作类都可以实例化,它们都实现了ICallable接口。此接口有一个Apply 方法,该方法将参数传递给操作并返回(异步)结果。每个实例都必须通过对模拟器的引用正确实例化;最简单的方法是在模拟器实例上调用Get 泛型方法。

如果您查看SimulatorBase.cs,在第101 行的Run 方法的实现中,您可以看到这是如何完成的。在这个方法中,T是操作的类; I是操作输入的类; O 是操作返回值的类。您可以使用基本相同的代码创建对象列表,然后使用不同的参数调用 Apply

【讨论】:

  • 感谢您的解释!是的,事实证明有一个单独的问题阻止了结果。我认为它们 存储在模拟器对象中。我对模拟器的资源估计感兴趣,而不是量子操作的输出。不幸的是,我不明白Run 方法在做什么。幸运的是,现在使用委托是可行的,所以希望我能解决这个问题。
【解决方案4】:

除了我所了解的一点,我什么都不懂,您可以使用非静态包装器,每个包装器都允许访问不同的 Qop 静态类。

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);
  }
}

【讨论】:

    【解决方案5】:

    在其类型被泛型定义的对象上调用方法需要您使用generic constraint,以确保使用的泛型类型定义了预期的方法。

    在其核心,这依赖于多态性,以确保即使特定类型可以变化,但众所周知,所有可用的泛型类型(可以通过约束进行限制)都包含您希望调用的特定方法。

    静态类和方法缺少此功能。它们不能继承,也不能实现接口,也不能通过方法参数传递它们(并且尝试通过泛型来实现不是解决方案)。无法在两个不同静态类的两个静态方法之间创建“类继承”链接;即使方法具有相同的签名,否则。

    还有其他方法吗?是的。按优先顺序排列:

    (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);
        }
    }
    

    这有效地“非静态化”静态代码,您现在可以依靠所有包装器实现/继承公共 BaseWrapper 并因此都实现 DoTheThing 方法的知识。

    然后你的通用方法可以依赖这个:

    public void DoTheGenericThing<T>(T obj) where T : IWrapper
    {
        obj.DoTheThing(123);
    }
    

    注意:在这种特殊情况下,您甚至不需要泛型。我假设您在这种情况下并不真正需要泛型,但由于答案可以适用于泛型和非泛型情况,因此我将泛型参数留在了解决方案中。在某些特定情况下,您仍然需要使用泛型,尽管我怀疑这不是其中之一。

    (3) 第三个但非常脏的选项是无论如何都使用反射来调用该方法,并假设您从不传入不具有预期的类型静态方法。但这是一种非常糟糕的实践方法,会充满错误,几乎无法调试,而且绝对不适合重构。

    【讨论】:

      【解决方案6】:

      也许您可以尝试使用接口来处理这种情况。类似的东西:

      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 类实现了 IQuantumOperation 接口

      【讨论】:

      • 引用是指当您直接引用某事或某人时,请使用常规文本作为您的答案正文
      猜你喜欢
      • 1970-01-01
      • 2021-10-18
      • 2011-07-27
      • 1970-01-01
      • 2020-01-15
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      相关资源
      最近更新 更多