【问题标题】:Get reference of value type C# [duplicate]获取值类型 C# 的引用 [重复]
【发布时间】:2012-09-04 14:44:01
【问题描述】:

可能重复:
How do I assign by “reference” to a class field in c#?

我想将我的值类型的引用复制到另一个值类型。不使用unsafe范式可以吗?

int a = 10;
int b = 15;
a = b; // BUT I WANT TO SET REFERENCE OF b !

这是我的真实例子。我创建枚举值类型并通过引用将其作为参数发送:

   public enum Test
{
    test1,
    test2,
    test3
}

public class MyTestClass1
{
    private Test _test;

    public MyTestClass1(ref Test test)
    {
        _test = test;
    }
}

public class Content
{
    public void Method()
    {
        Test e = Test.test1;
        /*some code*/
        var omyTestClass1 = new MyTestClass1(ref e);
        /*some code*/
        e = Test.test2;
        /*some code*/
        e = Test.test3;
    }
}

【问题讨论】:

  • 你为什么要那样做?

标签: c# unsafe value-type


【解决方案1】:

如果你的方法是真的:

public MyTestClass1 Method()
{
    Test e = Test.test1;
    /*some code*/
    var omyTestClass1 = new MyTestClass1(ref e);
    /*some code*/
    e = Test.test2;
    /*some code*/
    e = Test.test3;
    return omyTestClass1;
}

返回的值包含对已在堆栈上但现在不在堆栈上的值类型的引用。现在,如果您尝试访问该字段,您可以获得任何信息。

更糟糕的是,如果您写信给那个参考资料怎么办?如果该引用已存储在实际的调用堆栈中,而不是将其所有时间都花在寄存器中(并且我们可能可以安全地假设一个类中存在引用它的字段这一事实意味着它必须是那里),那么现在那里有什么?它可能是对对象的引用。它可能是一个返回地址。写入它可能会导致一些奇怪的 fandango-on-the-core 错误,很可能是在写入之后的一段时间,因此很难调试。

我们会失去一些我们拥有的基本保证。在您写入该值之后,几乎任何地方的任何代码都可能以某种奇怪的方式失败。

此时值得注意的是,C++ 和 .NET 本身(也就是说,您可以在 .NET 中执行的所有操作,包括您在 C# 中无法执行的操作)都允许本地引用和 ref 的返回值t 允许引用字段。

最接近的方法是捕获,就像使用 lambda 和匿名方法一样。在这里,本地人不是存储在堆栈中,而是存储在堆中,以允许它们在生命捕获的 lambda 时存活(并在它们中的最后一个被收集时被收集)。您当然可以使用它来维护对对象中以本地方式开始的内容的引用。

或者,您可以使用一个类来完成包装值类型的必要工作。例如,当我有足够相似的需求时,我使用了一个:

public sealed class SharedInt
{
  private int _value;
  /// <summary>Creates a new SharedInt with a value of zero.</summary>
  public SharedInt(){}
  /// <summary>Creates a new SharedInt.</summary>
  /// <param name="value">The initial value of the object.</param>
  public SharedInt(int value)
  {
    _value = value;
  }
  /// <summary>Returns the value of the SharedInt.</summary>
  public int Value
  {
    get { return _value; }
  }
  /// <summary>Returns the value of the SharedInt.</summary>
  /// <param name="ri">The SharedInt to cast.</param>
  /// <returns>An integer of the same value as the SharedInt.</returns>
  public static implicit operator int(SharedInt ri)
  {
    return ri._value;
  }
  /// <summary>Atomically increment the value of the SharedInt by one.</summary>
  /// <returns>The new value.</returns>
  public int Increment()
  {
    return Interlocked.Increment(ref _value);
  }
  /// <summary>Atomically decrement the value of the SharedInt by one.</summary>
  /// <returns>The new value.</returns>
  public int Decrement()
  {
    return Interlocked.Decrement(ref _value);
  }
  /// <summary>Atomically add a value to the SharedInt.</summary>
  /// <param name="addend">The number to add to the SharedInt.</param>
  /// <returns>The new value.</returns>
  public int Add(int addend)
  {
    return Interlocked.Add(ref _value, addend);
  }
  /// <summary>Atomically replace the value of the SharedInt, returning the previous value.</summary>
  /// <param name="value">The number to set the SharedInt to.</param>
  /// <returns>The old value.</returns>
  public int Exchange(int value)
  {
    return Interlocked.Exchange(ref _value, value);
  }
  /// <summary>Atomically subtract a value from the SharedInt.</summary>
  /// <param name="subtrahend">The number to subtract from the SharedInt.</param>
  /// <returns>The new value.</returns>
  public int Subtract(int subtrahend)
  {
    return Interlocked.Add(ref _value, -subtrahend);
  }
}

我需要一些你可能不需要的原子性保证,同样可能不需要你确实需要的一些东西,但它非常适合作为一种能够处理相同 int 的方法来自不同类别的价值。

如果你不介意每次都检查一个属性,你可以做一个更通用的版本。

public class TypedBox<T> where T : struct
{
  public T Value;
}

公开一个字段有相当大的缺点(这就是我们通常从不这样做的原因),但其中大多数不适用于这种情况(我们希望能够完全从外部),这意味着您甚至可以将Value 作为refout 参数传递。

【讨论】:

    【解决方案2】:

    如果不使用unsafe 或将值类型包装在结构/类中,您将无法做到这一点。

    来自MSDN

    将一个值类型变量分配给另一个会复制包含的值。

    【讨论】:

    • unsafe 也无法做到这一点。
    • @JonHanna Ah - 我只是假设因为他说他可以在这个问题中!感谢您指出这一点。
    【解决方案3】:

    我不确定我是否完全理解您,但您可以将您的 int 包装在某个引用类型中,例如长度为 1int[] 或 1 元组 Tuple&lt;int&gt;

    所以:

    var a = Tuple.Create(10);
    var b = Tuple.Create(15);
    a = b;
    

    由于Tuple&lt;&gt;不可变的,所以这是毫无意义的。

    但随后:

    var a = new[] { 10 };
    var b = new[] { 15 };
    a = b;
    // a and b reference the same object, and that'a a mutable object!
    b[0] = 17;
    // now a and b are still the same object, so also a[0] == 17
    

    注意:这与您使用的 ref 关键字无关。你似乎无缘无故地使用ref

    【讨论】:

    • @Abatonime 为什么你现在把那个链接带到这里?如果您首先将int 框起来,那么您可以将对该框的引用复制到另一个变量。然后ReferenceEquals 将确认两个(引用类型)变量引用同一个框。
    • 我发布此链接是因为您在回答中描述的内容看起来像拳击。所以这个 msdn 链接更详细地解释了拳击。
    • @Abatonime "装箱" an int 将其分配给(或将其作为)引用类型的变量,这是System.Int32 的基类之一,或接口之一System.Int32 实现。例如:object box1 = 10; IFormattable box2 = 15; var ref2ToBox1_reusesBoxNoNewBoxing = (IConvertible)box1;
    【解决方案4】:

    在同一方法中不可能有一个包含对另一个局部变量的引用的局部变量。 可能的类似情况是方法参数在调用方法中具有对局部变量的引用:

    void CallingMethod()
    {
        int a = 10;
        SomeMethod(ref a);
        ...
    }
    
    void SomeMethod(ref int b)
    {
        // here, the variable b holds a reference to CallingMethod's local variable "a".
    

    请注意,b 可以包含对局部变量以外的其他内容的引用。例如,您可以像这样调用 SomeMethod,在这种情况下,引用将指向堆上对象的字段:

    class MyClass
    {
        private int _a;
        public void AnotherCaller()
        {
            SomeMethod(ref _a);
            ...
        }
    }
    

    更新:Eric Lippert 偶尔会写下您所询问的功能,因为 CLR 确实支持它,而 C#可以支持它。看到这个答案,例如:https://stackoverflow.com/a/4923772/385844

    【讨论】:

    • CLR 支持本地和返回类型,而不是字段。
    • @JonHanna 感谢您提及这一点。我并不是要暗示 ref 字段是可能的。
    猜你喜欢
    • 2021-11-26
    • 2014-07-28
    • 1970-01-01
    • 2011-09-15
    • 1970-01-01
    • 2014-12-27
    • 2015-11-20
    • 1970-01-01
    • 2013-06-01
    相关资源
    最近更新 更多