在C#中引用变量?

时间:2009-09-14 07:46:47

标签: c#

在C ++中,我可以这样做:

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

然后,我总是知道p指向int数组i的第5个元素,不管我的内容是什么。

有没有办法在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参数允许您写入特定变量。但是非参数怎么样?你可以存储参考吗?你能保留一系列参考文献或字典吗?

似乎是否存在使用参数执行此操作的能力,应该有一种方法可以在没有参数的情况下执行此操作。

7 个答案:

答案 0 :(得分:18)

没有。将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来保存整数并使用该类的实例来存储变量。基本上,局部变量被提升为类中的字段并存储在堆中。

答案 1 :(得分:6)

只读数组引用:

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"

答案 2 :(得分:2)

在托管代码中使用引用而不是指针,因为垃圾收集器可以随时在内存中移动对象。

要引用它必须是对象的东西,所以你不能引用整数数组中的各个项。由于字符串是对象,因此只需复制数组中的引用即可引用各个字符串:

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"

答案 3 :(得分:1)

从 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"

答案 4 :(得分:0)

如果有人仍在寻找可能的解决方案。 如果你将每个数组包装到类中,那么可以通过引用数组将它分配给相同包装类的另一个实例。

但是这应该仅用作可能的概念证明。 我一般不建议使用此功能,但建议以更有效的方式重新设计代码。另外值得一提的是,您可以将数组分配给另一个数组作为其元素(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   , ]

希望这有助于某人。

答案 5 :(得分:0)

可以使用委托来实现这一目标。如今它甚至不需要很多代码:

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 Fiddle

注意:当然,在您拥有支持数组/列表/集合的示例中,仅存储索引而不是Func<T>就足够了。可能会更快。此外,某些列表实现(如LinkedList<T>)允许访问列表结构。 (挑剔:然后阵列本身就是参考文献的“列表”,因此s[1]sr[1]应该是等价的,sr因此在示例中完全是多余的。)

答案 6 :(得分:-1)

我怀疑你问的是错误的问题......

考虑以下C#代码:

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

在这种情况下,obj 引用与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"