#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 simpleMyRef<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>();
это присваивание, а не мутация.