【问题标题】:.NET C++: How to bind parameters to a delegate to create a new a zero-parameter delegate?.NET C++:如何将参数绑定到委托以创建新的零参数委托?
【发布时间】:2014-10-16 19:31:43
【问题描述】:

有什么方法可以将参数绑定到委托以创建新的零参数委托?

类似:

delegate void FnDelegate(int id, String^ name);
void Fn(int id, String^ name);

Delegate^ del = <some-function-name>(gcnew FnDelegate(&Fn), 1, "Name");

我注意到 Form::Invoke 可能使用了一些类似的机制,所以必须有一些工具:

delegate void FnDelegate(int id, String^ name);
Invoke(gcnew FnDelegate(&Fn), 1, "Name");

现在,我必须做一些非常尴尬的事情(对于每个函数!):

void Fn(int id, String^ name);

ref class FnWrapper
{
public:
   int     id;
   String^ name;

   void Execute()
   {
      Fn(id, name);
   }
};

delegate void VoidDelegate();
Delegate^ CreateFnDelegate(int id, String^ name)
{
   FnWrapper^ Wrapper = gcnew FnWrapper;
   Wrapper->id = id;
   Wrapper->name = name;
   return gcnew VoidDelegate(Wrapper, &FnWrapper::Execute);
}

必须有一种更优雅的方式来做到这一点。

【问题讨论】:

    标签: .net parameters delegates c++-cli bind


    【解决方案1】:

    您在这里尝试执行的操作称为变量捕获。

    在 C# 中,您可以通过定义一个内联委托来执行此操作,该委托将为您执行变量捕获:

    Action<int, string> delegateWithParams = ...
    Action delegateWithoutParams1 = delegate { delegateWithParams(7, "foo"); };
    // or if you like lambda syntax:
    Action delegateWithoutParams2 = () => delegateWithParams(7, "foo");
    

    C++/CLI 没有内联委托或 lambda,因此您必须手动进行变量捕获。这是我编写的一个类,用于帮助捕获变量和我的测试程序。

    如果你看过反编译的 C# 代码,你会发现 C# 编译器在捕获变量时基本上是在做同样的事情:它创建一个帮助类来存储捕获的变量,并且定义了不带参数的委托在该类上调用带参数的委托。

    void SomeMethod(int i, String^ s)
    {
        Debug::WriteLine("SomeMethod was called with integer {0} and string '{1}'", i, s);
    }
    
    generic<typename T1, typename T2>
    public ref class VariableCapture
    {
    private:
        Action<T1,T2>^ delegateWithParams;
        T1 item1;
        T2 item2;
    
        VariableCapture(Action<T1,T2>^ delegateWithParams, T1 item1, T2 item2)
        {
            this->delegateWithParams = delegateWithParams;
            this->item1 = item1;
            this->item2 = item2;
        }
    
        void RunDelegate()
        {
            this->delegateWithParams(item1, item2);
        }
    
    public:
        static Action^ Capture(
            Action<T1,T2>^ delegateWithParams, T1 item1, T2 item2)
        {
            VariableCapture<T1,T2>^ capture = 
                gcnew VariableCapture<T1,T2>(delegateWithParams, item1, item2);
            return gcnew Action(capture, &VariableCapture<T1,T2>::RunDelegate);
        }
    };
    
    int main(array<System::String ^> ^args)
    {
        Action<int, String^>^ delegateWithParams = 
            gcnew Action<int, String^>(&SomeMethod);
    
        Action^ delegateWithoutParams = 
            VariableCapture<int, String^>::Capture(delegateWithParams, 7, "foo");
    
        delegateWithoutParams();
    }
    

    输出:

    使用整数 7 和字符串 'foo' 调用 SomeMethod

    • 创建delegateWithoutParams 后,您无需保留对捕获对象或带参数的委托的引用:它们将通过delegateWithoutParams 中的引用保持活动状态。
    • 您需要为每个参数数量编写一个版本的 VariableCapture,以及为Action&lt;&gt;Func&lt;&gt; 编写单独的版本。
    • 由于这(基本上)与 C# 所做的相同,因此它的性能将与在 C# 中一样。 (没有反射开销。)

    【讨论】:

      【解决方案2】:

      C++ 对此有多种机制,boost::bind 变成了std::bind,现在我们有了 lambda。但它们不适用于托管类型。希望 C++/CLI 编译器的未来版本将添加 lambda 功能,在此之前,您使用辅助对象的方法几乎就是它(但您可以对其进行泛化以减少代码重复。将要调用的函数存储为委托,并使用参数类型的模板,可以使其更可重用。)

      请注意,更好的方法几乎可以简单地自动构建辅助对象,实际上并没有任何已知的方法可以完全不同。

      Form::Invoke 只是将所有参数粘贴在 array&lt;Object^&gt; 中,并在调用时使用反射......你也可以这样做,但它会严重影响性能。

      ref class AnyDelegateWrapper
      {
      public:
         System::Delegate^ target;
         array<System::Object^>^ args;
      
         void Execute()
         {
            target->DynamicInvoke(args);
         }
      };
      
      System::Action^ mg_bind(System::Delegate^ d, ... array<System::Object^>^ args)
      {
          AnyDelegateWrapper^ w = gcnew AnyDelegateWrapper();
          w->target = d;
          w->args = args;
          return gcnew System::Action(w, &AnyDelegateWrapper::Execute);
      }
      
      Action^ del = mg_bind(gcnew FnDelegate(&Fn), 1, "Name");
      

      【讨论】:

      • "Form::Invoke 只是将所有参数粘贴在一个数组 中,并在调用时使用反射......你也可以这样做,但它对性能的影响很大。"我该怎么做呢?我不关心性能。
      • @user3215177:我添加了一个代码 sn-p,但它没有被编译或测试。让我知道它是如何工作的。
      • 谢谢!正是我想要的。完美运行。 KISS 原则 - 漂亮、干净和通用。没有复制粘贴,而是根据你的代码写了我需要的代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-10
      • 2013-01-25
      • 2012-07-12
      • 1970-01-01
      • 1970-01-01
      • 2015-12-17
      相关资源
      最近更新 更多