C# 中的 Lambda 和 ref/out

Posted

技术标签:

【中文标题】C# 中的 Lambda 和 ref/out【英文标题】:Lambda in C# and ref/out 【发布时间】:2021-08-15 21:34:30 【问题描述】:

我有一些从与通信总线接口的 DLL 导入的类似 set/get 函数。理想情况下,这些都应该共享一个相同的包装器,做一些错误检查、互斥处理等。下面的代码 sn-p 是它的基本表示。它编译并且所有函数似乎都可以工作,除了 WrappedGet() 不修改引用的变量数据。想法?

        static UInt32 common_var = 0;

        // Reduce boilerplate by wrapping all calls to the DLL with this
        static private bool CommonWrapper<T1>(Action<T1> dll_function_handle, ref T1 data)
        
            // Commmon code for mutex handling etc
            /// .... 
            
            // Call to function handle, sometimes data is in-parameter, other times out-param
            dll_function_handle(data);
            
            return true;
        

        static private void DllSet(UInt32 data)  common_var = data; 
        static public void WrappedSet(UInt32 data)  CommonWrapper<UInt32>((UInt32 a) => DllSet(a), ref data); 

        static private void DllGet(ref UInt16 data) data = 1234; 
        // WrappedGet does not modify argument "data" because of the lambda? What to do?
        static public void WrappedGet(ref UInt16 data)  CommonWrapper<UInt16>((UInt16 a) => DllGet(ref a), ref data); 

编辑:

这行得通:

  static public void WrappedGet(ref UInt16 data) 
  
       UInt16 local = 0;
       CommonWrapper<UInt16>((UInt16) => DllGet(ref local), ref data);
       data = local;
  

这有点尴尬,这是正确的做法吗?

【问题讨论】:

dll_function_handle 是一个 Action&lt;&gt;... 因此,它的第一个也是唯一一个参数仅用于输入,并且底层方法执行的任何更改都将丢失。您最好重新考虑CommonWrapper:您可能需要再重载一个接受Func&lt;&gt; 委托而不是Action&lt;&gt; 委托才能返回结果。 使用Action&lt;&gt; 似乎不是问题,请参阅上面的编辑答案。另外,对于真实世界的代码,我使用Func&lt;&gt; 谁能帮我指出为什么我在这篇文章中得到了一个减分? 【参考方案1】:

您的代码中的问题是,Action 没有将参数作为ref,因此将在将data 的副本提供给DllGet 方法之前创建它。

//                    vvvvvvvvvvvvvvvvvvvvvvvvvvv 
CommonWrapper<UInt16>((UInt16 a) => DllGet(ref a), ref data);

您可以定义一个委托并在 lambda 中使用 ref,而不是 Action

所以你需要添加一个委托

private delegate void WrappedFunction<T>(ref T data);

并且需要在CommonWrapper使用它

//                                    vvvvvvvvvvvvvvvvvvv
static private bool CommonWrapper<T1>(WrappedFunction<T1> dll_function_handle, ref T1 data)

    // Your code here

您需要将ref 添加到 lambda

//                                                                      vvv
static public void WrappedGet(ref UInt16 data)  CommonWrapper<UInt16>((ref UInt16 a) => DllGet(ref a), ref data); 

所以整个代码如下所示:

private delegate void WrappedFunction<T>(ref T data);

// Reduce boilerplate by wrapping all calls to the DLL with this
static private bool CommonWrapper<T1>(WrappedFunction<T1> dll_function_handle, ref T1 data)

    // Commmon code for mutex handling etc
    // .... 
    
    // Call to function handle, sometimes data is in-parameter, other times out-param
    dll_function_handle(ref data);
    
    return true;


static private void DllSet(UInt32 data)  common_var = data; 
static public void WrappedSet(UInt32 data)  CommonWrapper<UInt32>((ref UInt32 a) => DllSet(a), ref data); 

static private void DllGet(ref UInt16 data) data = 1234; 
static public void WrappedGet(ref UInt16 data)  CommonWrapper<UInt16>((ref UInt16 a) => DllGet(ref a), ref data); 

【讨论】:

感谢您如此仔细地指出我的代码失败的地方。我尝试了这种方法并且它有效。大声思考,似乎委托函数是拼图中缺失的部分,因为它允许我们创建一个带有引用参数的函数原型。直接使用Func&lt;&gt; 时这是不可能的。我认为这是最紧凑、最简洁的解决方案,所以我选择它作为我接受的答案。【参考方案2】:

根据我的评论,这是与您的原始代码最相似的解决方案:

class TEST

    static UInt32 common_var = 0;

    // Reduce boilerplate by wrapping all calls to the DLL with this
    static private bool CommonWrapper<T1>(Action<T1> dll_function_handle, ref T1 data)
    
        dll_function_handle(data);

        return true;
    
    static private bool CommonWrapper<T1>(Func<T1, T1> dll_function_handle, ref T1 data)
    
        data = dll_function_handle(data);

        return true;
    

    static private void DllSet(UInt32 data)  common_var = data; 
    static public void WrappedSet(UInt32 data)  CommonWrapper<UInt32>((UInt32 a) => DllSet(a), ref data); 

    static private void DllGet(ref UInt16 data)  data = 1234; 
    static public void WrappedGet(ref UInt16 data)  CommonWrapper<UInt16>((UInt16 a) =>  DllGet(ref a); return a; , ref data); 

就个人而言,如果丢弃任何输入值,我会避免使用ref 参数。我会将参数声明为out 或声明该方法的返回类型;这样,Func&lt;T1,T1&gt; 应该可以替换为更简单的Func&lt;T1&gt;

【讨论】:

啊哈,你让 lambda 返回了。我尝试了代码并且它有效。不幸的是,我无法更改返回类型,已经有很多客户端代码使用这些函数。只是我们必须修改现有的函数内部,我们宁愿不修改 1000+ 行样板代码。

以上是关于C# 中的 Lambda 和 ref/out的主要内容,如果未能解决你的问题,请参考以下文章

C# 闭包中的 Lambda 表达式是啥?

C# 闭包中的 Lambda 表达式是啥?

聊聊 C++ 和 C# 中的 lambda 玩法

如何在 C# 中的键和值列表上使用 Lambda 函数获取最大值和最小值?

在c#中使用lambda获取嵌套列表中的特定元素

C#中的lambda表达式