Подтвердить что ты не робот

Ссылки на переменные в С#?

В С++ я могу сделать что-то вроде этого:

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

Тогда я всегда могу знать, что p указывает на 5-й элемент массива int i, независимо от содержимого i.

Есть ли способ сделать что-то подобное в С#?

Я понимаю, что это, вероятно, один из способов, в котором С# "защищает" нас от самих себя, поэтому я не ищу точного эквивалентного, а скорее похожее понятие... то есть, имея возможность ссылаться на содержимое какой-либо другой переменной, а не экземпляр самой переменной.

Вот мой пример использования, о котором я думаю. У меня есть массив строк. Я хотел бы иметь еще один массив ссылок на эти элементы массива. Что-то вроде этого (явно недействительный код):

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 позволяют вам писать в определенную переменную. Но как насчет непараметрических параметров? Можете ли вы сохранить ссылку? Можете ли вы сохранить массив ссылок или словарь?

Похоже, если присутствует возможность делать это с параметрами, должен быть способ сделать это без них.

4b9b3361

Ответ 1

Нет. Ввод кода unsafe в сторону, что позволяет удерживать указатели в ячейках памяти, нет способа сохранить ссылку на переменную в С#.

ref и out аргументы предоставляют единственное средство для ссылки, но вы нигде не можете их сохранить.

Вы можете обойти это ограничение, обернув поля в class и вместо этого используя его ссылку. Это то, что делает компилятор для захвата переменных в замыканиях:

Например, когда вы пишете:

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

Во второй строке компилятор должен будет удалить анонимный метод и сохранить его как отдельный метод в классе. Очевидно, что этот метод не будет иметь доступа к переменной integer. Это как-то нужно передать "ссылку" на переменную integer на этот анонимный метод. Поскольку это невозможно, он будет генерировать class с полем для хранения целого числа и использует экземпляр этого класса для хранения переменной. В основном, локальная переменная продвигается к полю в классе и хранится в куче.

Ответ 2

Ссылка на массив только для чтения:

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"

Ответ 3

В управляемых ссылках кода используются вместо указателей, так как сборщик мусора может перемещать объекты в памяти в любой момент.

Чтобы иметь ссылку на что-то, он должен быть объектом, поэтому вы не можете иметь ссылки на отдельные элементы в массиве integer. Поскольку строки являются объектами, вы можете иметь ссылки на отдельные строки, просто скопировав ссылки в массиве:

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"

Ответ 4

Если кто-то все еще ищет возможное решение. Играя с дженериками, вы можете обернуть каждый массив в класс, чтобы его можно было назначить другому экземпляру того же класса переноса, указав массив в него.

Но это нужно использовать только как доказательство концепции, которое можно сделать. Обычно я не рекомендую использовать это, но предлагаю более эффективно преобразовать код. Также стоит упомянуть, что вы просто можете назначить массив другому массиву в качестве ссылки на его элементы (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

Я подозреваю, что вы задаете неправильный вопрос...

Рассмотрим следующий код С#:

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

В этом случае obj ссылается на тот же объект, что и arr [5], так как оба arr [5] и obj являются ссылками.

Проблема с примером кода, который вы указали, заключалась в том, что когда ваша запись "s [1] =" двухточечная нуль "", вы фактически не меняете строку в памяти - вы делаете ссылку в точке массива к другой строке. В принципе, ваш код С# эквивалентен следующему в C:

char **s = malloc( ... );
... set 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"