Установка ссылки на поле-член в C#

#c# #pass-by-reference

#c# #передача по ссылке

Вопрос:

Я хотел бы назначить ссылку на поле участника. Но я, очевидно, не очень хорошо понимаю эту часть C #, потому что я потерпел неудачу 🙂 Итак, вот мой код:

 public class End {
    public string parameter;

    public End(ref string parameter) {
        this.parameter = parameter;
        this.Init();
        Console.WriteLine("Inside: {0}", parameter);
    }

    public void Init() {
        this.parameter = "success";
    }
}

class MainClass {
    public static void Main(string[] args) {
            string s = "failed";
        End e = new End(ref s);
        Console.WriteLine("After: {0}", s);
    }
}
  

Вывод:

 Inside: failed
After: failed
  

Как мне получить «успех» на консоли?

Заранее спасибо, dijxtra

Комментарии:

1. Кстати, логично, что вы получаете один и тот же результат вывода (однако я понимаю, что вы ожидаете, пока результат success будет напечатан), потому что обе Console.WriteLine строки выполняются после Init() метода.

Ответ №1:

Как указывали другие, вы не можете хранить ссылку на переменную в поле на C # или, действительно, на любом языке CLR.

Конечно, вы можете достаточно легко получить ссылку на экземпляр класса, содержащий переменную:

 sealed class MyRef<T>
{
  public T Value { get; set; }
}
public class End 
{
  public MyRef<string> parameter;
  public End(MyRef<string> parameter) 
  {
    this.parameter = parameter;
    this.Init();
    Console.WriteLine("Inside: {0}", parameter.Value);
  }
  public void Init() 
  {
    this.parameter.Value = "success";
  }
}
class MainClass 
{
  public static void Main() 
  {
    MyRef<string> s = new MyRef<string>();
    s.Value = "failed";
    End e = new End(s);
    Console.WriteLine("After: {0}", s.Value);
  }
}
  

Проще простого.

Комментарии:

1. Есть ли MyRef в библиотеке? Похоже, это обычный сценарий использования, включающий типы значений.

2. Существует StrongBox<T> learn.microsoft.com/en-us/dotnet/api /…

Ответ №2:

Здесь действительно две проблемы.

Во-первых, как уже говорилось на других плакатах, вы не можете строго делать то, что хотите (как вы можете сделать с C и тому подобным). Однако — поведение и намерение по-прежнему легко выполнимы в C # — вам просто нужно сделать это способом C #.

Другая проблема заключается в вашей неудачной попытке попытаться использовать строки, которые, как упоминалось в одном из других плакатов, являются неизменяемыми и по определению копируются.

Итак, сказав это, ваш код можно легко преобразовать в это, что, я думаю, делает то, что вы хотите:

 public class End
{
    public StringBuilder parameter;

    public End(StringBuilder parameter)
    {
        this.parameter = parameter;
        this.Init();
        Console.WriteLine("Inside: {0}", parameter);
    }

    public void Init()
    {
        this.parameter.Clear();
        this.parameter.Append("success");
    }
}

class MainClass
{
    public static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder("failed");
        End e = new End(s);
        Console.WriteLine("After: {0}", s);
    }
}
  

Комментарии:

1. Это решение является самым элегантным из предложенных здесь, спасибо 🙂

2. Информативные вопросы и ответы, но я должен не согласиться, использование StringBuilder таким образом нарушает его семантическую цель и, возможно, дороже. Возможно, это кратчайший путь с точки зрения локализации, но он идет «против зерна» среды CLR. Eric’s simple MyRef<T> более четко передает цель и имеет дополнительное преимущество работы с любым заданным типом. Очень приятно.

3. @MarcL. — Да, я бы согласился с этим. Мой подход был определенно быстрым и грязным, основанным в основном на том, что я воспринял как (негативную) реакцию на вопрос при первоначальной публикации.

Ответ №3:

Похоже, что вы пытаетесь сделать здесь, это сделать поле ссылкой на другое место хранения. По сути, у вас есть ref поле так же, как у вас есть ref параметр. В C # это невозможно.

Одна из основных проблем, связанных с этим, заключается в том, что для проверки CLR (и C #) должна быть способна доказать, что объект, содержащий поле, не будет жить дольше, чем местоположение, на которое он указывает. Обычно это невозможно, поскольку объекты находятся в куче, и a ref может легко указывать на стек. Эти два места имеют очень разную семантику жизненного цикла (куча обычно длиннее стека), и, следовательно ref , нельзя доказать, что промежуток между ними является действительным.

Комментарии:

1. Не могли бы вы сделать это, захватив оригинал string s в замыкании?

2. @asawyer: Действительно, хотя это просто причудливый способ сказать «выделить класс, у которого есть поле string, а затем сохранить ссылку на класс». Если это то, что вы хотите сделать, просто сделайте это.

3. @Eric Это имеет смысл. Спасибо!

Ответ №4:

Если вы не хотите вводить другой класс, например MyRef or StringBuilder , потому что ваша строка уже является свойством в существующем классе, вы можете использовать Func и Action для достижения результата, который вы ищете.

 public class End {
    private readonly Func<string> getter;
    private readonly Action<string> setter;

    public End(Func<string> getter, Action<string> setter) {
        this.getter = getter;
        this.setter = setter;
        this.Init();
        Console.WriteLine("Inside: {0}", getter());
    }

    public void Init() {
        setter("success");
    }
}

class MainClass 
{
    public static void Main(string[] args) 
    {
        string s = "failed";
        End e = new End(() => s, (x) => {s = x; });
        Console.WriteLine("After: {0}", s);
    }
}
  

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

     /// <summary>
    /// Convert a lambda expression for a getter into a setter
    /// </summary>
    public static Action<T, U> GetSetter<T,U>(Expression<Func<T, U>> expression)
    {
        var memberExpression = (MemberExpression)expression.Body;
        var property = (PropertyInfo)memberExpression.Member;
        var setMethod = property.GetSetMethod();

        var parameterT = Expression.Parameter(typeof(T), "x");
        var parameterU = Expression.Parameter(typeof(U), "y");

        var newExpression =
            Expression.Lambda<Action<T, U>>(
                Expression.Call(parameterT, setMethod, parameterU),
                parameterT,
                parameterU
            );

        return newExpression.Compile();
    }
  

Ответ №5:

В этой строке:

 this.parameter = parameter;
  

… вы копируете параметр метода в член класса parameter . Затем Init() вы снова присваиваете значение «успех» члену класса parameter . В вашей консоли.Writeline, затем вы записываете значение параметра метода «сбой», потому что вы никогда фактически не изменяете параметр метода.

То, что вы пытаетесь сделать — так, как вы пытаетесь это сделать, — невозможно, я верю в C #. Я бы не стал пытаться передавать a string с ref модификатором.

Ответ №6:

Как ответил JaredPar, вы не можете.

Но проблема отчасти в том, что строка неизменяема. Измените свой параметр на значение of class Basket { public string status; } , и ваш код будет в основном работать. ref Ключевое слово не требуется, просто измените parameter.status .

И, конечно, есть другой вариант Console.WriteLine("After: {0}", e.parameter); . Сделайте перенос параметра в свойство (только для записи).

Комментарии:

1. Мне вообще не нравится предположение, что оно имеет какое-либо отношение к неизменности строки, что довольно часто встречается в вопросах, связанных с присвоением строк. Переназначение не является мутацией, изменяемый тип или нет. Подразумевать, что это имеет какое-либо отношение к неизменности, является искажением, вы не согласны? (Хотя предложение инкапсулировать значение в класс допустимо.)

2. Снова не эта старая собака…. Это единственное переназначение, потому что строки неизменяемы.

3. @Steve, чтобы было ясно, вы должны использовать переназначение, потому что вы не можете изменить исходную строку. Но моя большая точка зрения заключается в том, что назначение — это назначение, сам тип может быть изменяемым или неизменяемым, и поведение назначения x = y; будет таким же. Более важным моментом является то, что мое понимание вопроса заключается в том, что сам тип не имеет значения, он хотел сохранить ссылку или псевдоним на переданную исходную переменную. С этой целью все ответы подтверждают, что это невозможно напрямую, но предлагают способы работы в системе типов для достижения общей цели.

4. (Ба, извините за педантичную мыльницу, 1 со всех сторон.)

5. В качестве примера, иллюстрирующего точку зрения Энтони, рассмотрим поле типа List<string>, а не типа string . Списки изменчивы. Тем не менее, obj.StringList = new List<string>(); это присваивание, а не мутация.