簡體   English   中英

引用 C# 中的變量?

[英]References to variables in C#?

在 C++ 中,我可以這樣做:

int i[10] = new int[10];
int *p = &i[5];

然后,我總是可以知道 p 指向 int 數組 i 的第 5 個元素,而不管 i 的內容如何。

有沒有辦法在 C# 中做類似的事情?

我意識到這可能是 C#“保護”我們免受我們自己傷害的一種方式,所以我不是在尋找一個確切的等價物,而是一個類似的概念......也就是說,能夠參考某些內容其他變量,而不是變量本身的實例。

這是我正在考慮的用例。 我有一個字符串數組。 我想要對這些數組元素的另一個引用數組。 像這樣(顯然不是有效代碼):

string[] s = new string[] { "one", "two", "three", "four", "five", "six" };
stringref[] sr = new stringref[] { &s[0], &s[1], &s[2], &s[3], &s[4], &s[5] };

Console.WriteLine(sr[1]); // == "two"
s[1] = "two point zero";
Console.WriteLine(sr[1]); // == "two point zero"

當然, ref 參數可以做到這一點,而 out 參數允許您寫入特定變量。 但是非參數呢? 你能存儲一個參考嗎? 你能保留一組參考文獻或字典嗎?

似乎如果存在使用參數的能力,那么應該有一種方法可以在沒有參數的情況下做到這一點。

不。將unsafe代碼放在一邊,它允許保存指向內存位置的指針,沒有辦法在 C# 中存儲對變量的引用。

refout參數提供了獲取引用的唯一方法,但您無法將它們保存在任何地方。

您可以通過將字段包裝在class並改用其引用來解決此限制。 這是編譯器在閉包中捕獲變量的作用:

例如,當你寫:

int integer = 0;
Action<int> method = i => Console.WriteLine(i + integer);
integer = 42;
method(100); // prints 142, not 100

在第二行中,編譯器將不得不取出匿名方法並將其作為單獨的方法存儲在類中。 顯然,該方法無法訪問integer變量。 它以某種方式需要將integer變量的“引用”傳遞給該匿名方法。 由於這是不可能的,它將生成一個帶有字段的class來保存整數,並使用該類的實例來存儲變量。 基本上,局部變量被提升為類中的一個字段並存儲在堆中。

只讀數組引用:

class ArrayRef<T>
{
   private T[] array;
   private int index;

   public ArrayRef(T[] array, int index)
   {
      this.array = array;
      this.index = index;
   }

   public static implicit operator T(ArrayRef self)
   {
      return self.array[self.index];
   }
}

var s = new string[] { "one", "two", "three", "four", "five", "six" };
var sr = new ArrayRef<string>[] { new ArrayRef<string>(s, 0), new ArrayRef<string>(s, 1), new ArrayRef<string>(s, 2), new ArrayRef<string>(s, 3), new ArrayRef<string>(s, 4), new ArrayRef<string>(s, 5) };

Console.WriteLine(sr[1]); // == "two"
s[1] = "two point zero";
Console.WriteLine(sr[1]); // == "two point zero"

在托管代碼中使用引用而不是指針,因為垃圾收集器可以隨時在內存中移動對象。

要引用某個東西,它必須是一個對象,因此您不能引用整數數組中的各個項目。 由於字符串是對象,您可以通過復制數組中的引用來引用單個字符串:

string[] s = new string[] { "one", "two", "three", "four", "five", "six" };
string[] sr = new string[] { s[0], s[1], s[2], s[3], s[4], s[5] };

但是,由於字符串是不可變對象,因此您只能使用引用來讀取項目。 如果您將字符串分配給 sr 數組中的引用,您將覆蓋該引用而不是更改它指向的對象。

如果要更改對象,則必須具有可變對象。 例如:

StringBuilder[] s = new StringBuilder[] {
   new StringBuilder("one"),
   new StringBuilder("two"),
   new StringBuilder("three"),
};
StringBuilder[] sr = new StringBuilder[] { s[0], s[1], s[2] };

Console.WriteLine(s[1]); // == "two"
sr[1].Append(" point zero");
Console.WriteLine(s[1]); // == "two point zero"

從 C#7 開始,可以為數組中的元素或對象中的字段定義局部引用:

string[] s = new string[] { "one", "two", "three", "four", "five", "six" };
ref string sr1 = ref s[1];  // a ref local to an element in an array
Console.WriteLine(sr1); // == "two"
sr1 = "two point zero";
Console.WriteLine(s[1]); // == "two point zero"

如果有人仍在尋找可能的解決方案。 如果您將每個數組包裝到類中,則可以很少使用泛型,這樣就可以通過將數組引用到其中來將其分配給同一包裝類的另一個實例。

但這應該僅用作可以做到這一點的概念證明。 我通常不建議使用它,但建議以更有效的方式重新設計代碼。 另外值得一提的是,您只需將數組分配給另一個數組作為對其元素的引用(DOH)。

這里所說的是通用引用數組到數組數據的示例代碼:

using System;
using System.Diagnostics;

public class RefArray<TElem>
{
  TElem[] data;

  /// <summary>Initializes RefArray by creating Ref&lt;T>[]data from values.</summary>
  public RefArray(TElem[] values)
  {
    data = values;
  }

  /// <summary>Creates reference RefArray pointing to same RefArray&lt;T> data.</summary>
  public RefArray(RefArray<TElem> references)
  {
    this.data = references.data;
  }

  public int Length
  {
    get { return data.Length; }
  }

  public TElem this[int index]
  {
    get
    {
      return data[index];
    }
    set
    {
      data[index] = value;
    }
  }
}

public static class RefArrayTest
{

  public static void Usage()
  {

    // test ints (struct type)
    RefArray<int> c = new RefArray<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
    RefArray<int> d = new RefArray<int>(c);
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3]));
    c[3] = 1111;
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3]));
    d[3] = 2222;
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3]));
    d[3] = c[3] + 3333;
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3]));

    // test strings (class type)
    RefArray<string> a = new RefArray<string>(new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" });
    RefArray<string> b = new RefArray<string>(a);
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3]));
    a[3] = "set a";
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3]));
    b[3] = "set b";
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3]));
    a[3] = b[3] + " + take b set a";
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3]));

    // proof of no point since
    string[] n1 = new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
    string[] n2 = n1;
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3]));
    n1[3] = "set n1";
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3]));
    n2[3] = "set n2";
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3]));
    n1[3] = n2[3] + " + take n2 set n1";
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3]));
  }

}

此外,如果引用的元素需要亂序,您可以添加通用 Ref_T 類來包裝每個值作為引用:

using System;
using System.Text;
using System.Diagnostics;

public class Ref_T<TValue>
{  
  public TValue data;
  public Ref_T(TValue value)
  {
    this.data = value;
  }
}

public class RefArray<TElem>
{
  public readonly Ref_T<TElem>[] data;

  /// <summary>Initializes RefArray by creating Ref&lt;T>[]data from values.</summary>
  public RefArray(TElem[] values)
  {
    data = new Ref_T<TElem>[values.Length];
    for (int i = 0; i < values.Length; i++)
    {
      // creates reference members
      data[i] = new Ref_T<TElem>(values[i]); 
    }
  }

  /// <summary>Creates reference RefArray pointing to same RefArray&lt;T> data.</summary>
  public RefArray(RefArray<TElem> references)
  {
    data = references.data;
  }

  /// <summary>Creates reference RefArray pointing to same Ref&lt;T>[] references.</summary>
  public RefArray(Ref_T<TElem>[] references)
  {
    data = references;
  }

  public int Length
  {
    get { return data.Length; }
  }

  public TElem this[int index]
  {
    get { return data[index].data; }
    set { data[index].data = value; }
  }

  public override string ToString()
  {
    StringBuilder sb = new StringBuilder();
    int count = data.Length;
    for (int i = 0; i < count; i++ )
      sb.Append(string.Format("[{0}]:{1,-4}, ", i, data[i].data));
    return sb.ToString();
  }

  public static implicit operator Array(RefArray<TElem> a)
  {
    return a.data;
  }
}

public static class RefArrayTest
{

  public static void Usage()
  {    
    // test ints (struct type) out of order
    // initialize 
    RefArray<int> e = new RefArray<int>(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
    // reference e out of order
    RefArray<int> f = new RefArray<int>(new Ref_T<int>[] 
      { e.data[8], e.data[6], e.data[4], e.data[2], e.data[0], 
        e.data[9], e.data[7], e.data[5], e.data[3], e.data[1] 
      });

    Debug.WriteLine("Initial: ");
    Debug.WriteLine("e: [" + e + "]");
    Debug.WriteLine("f: [" + f + "]\r\n");

    e[3] = 1111;
    Debug.WriteLine("e[3] = 1111;");
    Debug.WriteLine("e: [" + e + "]");
    Debug.WriteLine("f: [" + f + "]\r\n");

    f[3] = 2222;
    Debug.WriteLine("f[3] = 2222;");
    Debug.WriteLine("e: [" + e + "]");
    Debug.WriteLine("f: [" + f + "]\r\n");

    f[3] = e[3] + 3333;
    Debug.WriteLine("f[3] = e[3] + 3333;");
    Debug.WriteLine("e: [" + e + "]");
    Debug.WriteLine("f: [" + f + "]\r\n");

    Array.Reverse(f);
    Debug.WriteLine("Array.Reverse(f);");
    Debug.WriteLine("e: [" + e + "]");
    Debug.WriteLine("f: [" + f + "]\r\n");
  }

}

輸出:

Initial: 
e: [[0]:0   , [1]:1   , [2]:2   , [3]:3   , [4]:4   , [5]:5   , [6]:6   , [7]:7   , [8]:8   , [9]:9   , ]
f: [[0]:8   , [1]:6   , [2]:4   , [3]:2   , [4]:0   , [5]:9   , [6]:7   , [7]:5   , [8]:3   , [9]:1   , ]

e[3] = 1111;
e: [[0]:0   , [1]:1   , [2]:2   , [3]:1111, [4]:4   , [5]:5   , [6]:6   , [7]:7   , [8]:8   , [9]:9   , ]
f: [[0]:8   , [1]:6   , [2]:4   , [3]:2   , [4]:0   , [5]:9   , [6]:7   , [7]:5   , [8]:1111, [9]:1   , ]

f[3] = 2222;
e: [[0]:0   , [1]:1   , [2]:2222, [3]:1111, [4]:4   , [5]:5   , [6]:6   , [7]:7   , [8]:8   , [9]:9   , ]
f: [[0]:8   , [1]:6   , [2]:4   , [3]:2222, [4]:0   , [5]:9   , [6]:7   , [7]:5   , [8]:1111, [9]:1   , ]

f[3] = e[3] + 3333;
e: [[0]:0   , [1]:1   , [2]:4444, [3]:1111, [4]:4   , [5]:5   , [6]:6   , [7]:7   , [8]:8   , [9]:9   , ]
f: [[0]:8   , [1]:6   , [2]:4   , [3]:4444, [4]:0   , [5]:9   , [6]:7   , [7]:5   , [8]:1111, [9]:1   , ]

Array.Reverse(f);
e: [[0]:0   , [1]:1   , [2]:4444, [3]:1111, [4]:4   , [5]:5   , [6]:6   , [7]:7   , [8]:8   , [9]:9   , ]
f: [[0]:1   , [1]:1111, [2]:5   , [3]:7   , [4]:9   , [5]:0   , [6]:4444, [7]:4   , [8]:6   , [9]:8   , ]

希望這可以幫助某人。

可以使用委托來實現這一點。 現在它甚至不需要很多代碼:

int i[10] = new int[10];
int *p = &i[5];

變成:

int[] i = new int[10];
Func<int> p = () => i[5];

執行p()總是返回第 6 個元素(索引 5,基於 0)。

我們還可以加點糖:

public class Reference<T>
{
  private readonly Func<T> referenceFunc;

  public Reference(Func<T> referenceFunc)
  {
    this.referenceFunc = referenceFunc;
  }

  public T Value => this.referenceFunc();

  public static implicit operator T(Reference<T> reference)
  {
    return reference.Value;
  }

  public static implicit operator Reference<T>(Func<T> referenceFunc)
  {
    return new Reference<T>(referenceFunc);
  }

  public override string ToString()
  {
    return this.Value?.ToString();
  }
}

public static class ReferenceExtensions
{
  public static void Add<T>(
      this ICollection<Reference<T>> collection,
      Func<T> referenceFunc)
  {
    collection.Add(referenceFunc);
  }
}

應用於您的示例:

string[] s = new string[] { "one", "two", "three", "four", "five", "six" };
IReadOnlyList<Reference<string>> sr = new List<Reference<string>>
{
  () => s[0],   
  () => s[1],
  () => s[2],
  () => s[3],
  () => s[4],
  () => s[5],
};

Console.WriteLine(sr[1]); // == "two"
s[1] = "two point zero";
Console.WriteLine(sr[1]); // == "two point zero"

.Net小提琴

注意:當然,在您有一個支持數組/列表/集合的示例中,僅存儲索引而不是Func<T>就足夠了。 可能會更快。 還有一些列表實現,比如LinkedList<T> ,允許訪問列表結構。 (挑剔:然后數組本身又是一個引用的“列表”,因此s[1]sr[1]應該是等價的,因此在示例中sr完全是多余的。)

我懷疑你問錯了問題...

考慮以下 C# 代碼:

MyClass[] arr = new MyClass[] { MyClass(1), MyClass(2) .... };
MyClass obj = arr[5];

在這種情況下,物鏡指相同的對象作為ARR [5],由於兩個ARR [5]與obj引用。

您提供的示例代碼的問題在於,當您寫入“s[1] =“兩點零””時,您實際上並未更改內存中的字符串 - 您將數組中的引用指向了不同的細繩。 基本上,您的 C# 代碼等效於 C 中的以下內容:

char **s = malloc( ... );
... set s's members
char *sr = malloc( ... );
sr[1] = s1;
s[1] = "two point zero";
// now, s[1] is "two point zero" while sr[1] is still "one"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM