如果“错误 CS1628:无法在匿名方法、lambda 或查询表达式中使用 in ref 或 out 参数”,如何在线程中使用 ref 参数?

Posted

技术标签:

【中文标题】如果“错误 CS1628:无法在匿名方法、lambda 或查询表达式中使用 in ref 或 out 参数”,如何在线程中使用 ref 参数?【英文标题】:How to use a ref parameter in a thread if "Error CS1628: Cannot use in ref or out parameter inside an anonymous method, lambda or query expression"? 【发布时间】:2021-10-28 10:27:50 【问题描述】:

我正在尝试编写一个补间类,为此我有一个静态的Tweener.TweenTo 方法。在其中,我们启动一个线程以不阻碍外部操作。该方法如下所示:

public static void TweenTo<T>(ref ITweenable<T> obj, T target, double ms)

    new System.Threading.Thread(() => 
        System.Threading.Thread.Sleep(5000)
        obj.DoStuff(5,5) //throws exception because obj is a ref parameter
    ).Start()

我知道在 lambda 中使用 ref 参数意味着它可能是一个悬空引用,但我需要能够使用它以防用户尝试将值类型或结构传递给方法。我尝试过使用参数化线程启动,但这会将事情强制到一个我不能使用的对象(拆箱等)。我也尝试过使用包含指向它的指针的包装类,但这会在以后遇到复杂情况。

我想要一种在线程中使用此 ref 参数的方法,并理想地在其中保留其生命周期。

任何帮助表示赞赏:D

编辑:Olivier 的答案接近我的需要,但 MyClass 在某些情况下会是一个结构,并且它会尽可能地复制。这意味着它将丢失引用并从错误的实例中给出值。

编辑 2:示例结构

public struct MyStruct : ITweenable<T> 
    int x;
    int y;
    public MyStruct(int X) 
        this.x=X;
    
    public void DoStuff(int newX, int newY) 
         this.x=newX;
         this.y=newY;
    


public interface ITweenable<T> 
    void DoStuff(int newX, int newY);

编辑 3:我还没有测试过这个,所以当我有的时候我会给出它作为答案 - 现在已经测试过,不起作用:

public static void TweenTo<T>(ITweenable<T> obj, T target, double ms)

Func<ITweenable<T>> getTween = ()=>return obj;
    new System.Threading.Thread(() => 
        System.Threading.Thread.Sleep(5000)
        getTween().DoStuff(5,5);
    ).Start()

【问题讨论】:

这里为什么需要使用ref?而且您可能应该使用任务库而不是线程。 我宁愿使用Parameterized ThreadStart 并将其显式转换回来。你不会有装箱\拆箱,只有转换,因为我猜ITweenable&lt;T&gt; obj 是一个引用类型(装箱\拆箱只发生在值类型上)。此外,在大多数情况下,使用 Async\Await over Threads 的任务在资源效率和代码可读性方面更可取。 Ref 是必需的以保留结构引用(请参阅上面的编辑) “因为 obj 是 ref 参数而引发异常”是什么意思?什么例外? minimal reproducible example 可以更轻松地为您提供帮助,同时满足精确 要求。 (顺便说一句,我期望 ITweenable&lt;T&gt; 是一个接口,因此不是一个值类型。即使实现是一个值类型,使用接口会导致装箱,参数的编译时类型需要为ITweenable&lt;T&gt;。) 【参考方案1】:

我建议传递Action&lt;ITweenable&lt;T&gt;&gt; 以启用分配。像这样:

public static void TweenTo<T>(ITweenable<T> obj, Action<ITweenable<T>> update, T target, double ms)

    new System.Threading.Thread(() =>
    
        obj.DoStuff();
        update(new Tweenable<T>());
    ).Start()

【讨论】:

【参考方案2】:

使用中间本地变量

不考虑设计和使用ref参数的原因,也不考虑任何线程并发和互锁管理,只使用中间本地var:

public static void TweenTo<T>(ref ITweenable<T> obj, T target, double ms)

  var instance = obj;
  new System.Threading.Thread(instance.DoStuff).Start();

测试

public interface ITweenable<T>

  void DoStuff();


public class MyClass : ITweenable<int>

  public void DoStuff()
  
    Console.WriteLine("It works!");
    Console.WriteLine("Press any key to exit the side thread.");
    Console.ReadKey();
  


static private void Test()

  var instance = (ITweenable<int>)new MyClass();
  TweenTo(ref instance, 10, 20);
  Console.WriteLine("Main thread ended.");

输出

Main thread ended.
It works!
Press any key to exit the side thread.

问题中添加的结构和代码备注

对于提供的代码和案例,它的工作原理相同:

public interface ITweenable<T>

  T X  get; 
  T Y  get; 
  void DoStuff(T newX, T newY);


public struct MyStruct : ITweenable<int>

  public int X  get; private set; 
  public int Y  get; private set; 
  public void DoStuff(int newX, int newY)
  
    Thread.Sleep(2000);
    X = newX;
    Y = newY;
    Console.WriteLine("It works!");
  


public static void TweenTo<T>(ref ITweenable<T> obj, T target, double ms)

  var instance = obj;
  new System.Threading.Thread(() => instance.DoStuff((T)(object)10, (T)(object)10)).Start();
  Console.WriteLine("Exiting TweenTo.");

测试

static private void Test()

  var instance = (ITweenable<int>)new MyStruct();
  Console.WriteLine("X is " + instance.X);
  TweenTo(ref instance, 10, 20);
  Console.WriteLine("Main thread ended.");
  Console.WriteLine("Wait for the 'It Works' and press any key to continue main thread.");
  Console.ReadKey();
  Console.WriteLine("X is now " + instance.X);
  Console.ReadKey();

输出

X is 0
Exiting TweenTo.
Main thread ended.
Wait for the 'It Works' and press any key to continue main thread.
It works!
X is now 10

【讨论】:

这与我需要的非常接近,但 MyClass 在某些情况下会是一个结构,并且它会尽可能地复制。这意味着它将丢失引用并从错误的实例中给出值。 这完全违背了ref 的目的,并不是说OP 无论如何都在使用它。 @JohnWilliams 为什么需要ref?您没有分配给obj 如果 obj 是一个结构,那么当传递给方法时,它将成为传递的一个单独的实例。这意味着无法进行修改。 不幸的是,我最初写的问题很糟糕。我需要将几个参数传递给 ITweenable。道歉【参考方案3】:

引用值不能跨线程或在任何延迟执行中使用,因为 .NET 无法强制执行其内存管理。

    ref 中的结构位于调用函数的堆栈中,并且只能在该函数调用的范围内使用。

    结构是其他类中的字段,引用是指向对象内存中间某处的指针。垃圾收集器无法跟踪这些指针,并且它们的寿命不得超过创建此引用的函数调用。此函数调用在其堆栈中具有指向对象的原始指针,垃圾收集器可以通过该指针跟踪该对象。

因此,简而言之,需要可补间的对象不能是结构。唯一的解决方案是使包含此结构的类可补间。或者,如果 tweenables 包含在数组中,请实现这样的帮助器:

public class TweenableArray<T> : ITweenable<T>

    private readonly ITweenable<T>[] _data;

    public TweenableArray(ITweenable<T>[] data)
    
        _data = data
    

    void DoStuff(int newX, int newY)
    
        for (var i = 0; i < _data.Length; i++)
            _data[i].DoStuff(newX, newY)
    

前提是newX和newY可以用于整个数组。

【讨论】:

以上是关于如果“错误 CS1628:无法在匿名方法、lambda 或查询表达式中使用 in ref 或 out 参数”,如何在线程中使用 ref 参数?的主要内容,如果未能解决你的问题,请参考以下文章

检查记录是不是存在,如果是,则“更新”,如果不“插入”

Jquery“如果这个和如果那个”然后这样做

如果上游启动,Nginx 绕过缓存,如果关闭则使用缓存

如果新文件不存在则写入新文件,如果存在则追加到文件

数据库:如果存在则“更新”如果不存在则插入 [重复]

如果字段存在必须为真,但如果不存在则必须像真一样通过