开发者

References to variables in C#?

开发者 https://www.devze.com 2023-04-12 13:06 出处:网络
In C++, I can do something like this: int i[10] = new int[10]; int *p = &i[5]; Then, I can always know that p points to the 5th element of int array i, regardless of i\'s contents.

In C++, I can do something like this:

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

Then, I can always know that p points to the 5th element of int array i, regardless of i's contents.

Is there any way to do something similar in C#?

I realize this is likely one of the ways in which C# "protects" us from ourselves, so I'm not looking for an exact equivalent, but rather a similar concept... that is, being able to refer to the contents of some other variable, rather than the instance of the variable itself.

Here's my use case I'm thinking of. I have an array of strings. I would like to have another array of references to those array elements. Something like this (obviously not valid code):

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"

Certainly, ref parameters do this, and out parameters allow you to write to a specific variable. But what about non-parameters? Can you store a ref? Can you keep an array of refs or a dictionary?

It seems like if th开发者_StackOverflow中文版e ability to do it with parameters is present, there should be a way to do it without them.


No. Putting unsafe code aside, which does allow holding pointers to memory locations, there's no way to store a reference to a variable in C#.

ref and out arguments provide the only means to take a reference but you can't save them anywhere.

You can workaround this limitation by wrapping fields in a class and using its reference instead. This is what the compiler does to capture variables in closures:

For instance, when you write:

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

In the second line, the compiler will have to take out the anonymous method and store it as a separate method in the class. Obviously, that method won't have access to integer variable. It somehow needs to pass a "reference" to integer variable to that anonymous method. Since it's not possible, it'll generate a class with a field to hold an integer and uses an instance of that class to store the variable. Basically, the local variable is promoted to a field in a class and is stored in the heap.


A read-only array reference:

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"


Since C#7 it is possible to define a ref local to an element in an array or a field in an object:

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"


In managed code references are used instead of pointers, as the garbage collector can move objects around in memory at any moment.

To have a reference to something it has to be an object, so you can't have references to the individual items in an integer array. As strings are objects, you can have references to the individual strings by just copying the references in the array:

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] };

However, as strings are immutable objects you can only use the references to read the items. If you assign a string to a reference in the sr array, you will overwrite the reference instead of changing the object that it points to.

If you want to change the objects, you will have to have mutable objects. For example:

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"


It's possible to achieve this using delegates. Nowadays it doesn't even require a lot of code:

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

becomes:

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

performing p() always returns the 6th element (index 5, 0 based).

We can also add some sugar:

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);
  }
}

Applied to your example:

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

Note: Of course, in the example where you have a backing array/list/collection it'd be enough just to store the index instead of a Func<T>. Might be faster. Also some list implementations, like LinkedList<T>, allow access to the list structure. (Nitpicking: Then again the array itself is a "list" of references and thus s[1] and sr[1] should be equivalent, sr thus completely superfluous in the example.)


If someone still is looking for possible solution. Playing little with generics it is possible if you wrap each array into class so it can be assigned to another instance of same wrap class by referencing array into it.

But this should be used only as proof of concept that is possible to do it. I generally wouldn't recommend use of this, but would suggest redesigning the code in more efficient way. Also its worth to mention you simply can assign array to another array as reference to its elements (DOH).

This being said here is the example code of generic reference array to array data:

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]));
  }

}

Also if referenced elements need to be out of order you could add generic Ref_T class to wrap each value as reference:

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");
  }

}

outputs:

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   , ]

Hope this helps someone.


I suspect you're asking the wrong question...

Consider the following C# code:

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

In this case, obj is referring to the same object as arr[5], since both arr[5] and obj are references.

The issue with the sample code you provided was that when your write "s[1] = "two point zero"", you aren't actually changing the string in memory - you're making the reference in the array point to a different string. Basically, your C# code is equivalent to the following in 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"
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号