可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'd like to assign a reference to a member field. But I obviously do not understand this part of C# very well, because I failed :-) So, here's my code:
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);
}
}
Output is:
Inside: failed
After: failed
How do I get "success" on the console?
Thanks in advance,
dijxtra
回答1:
There are really two issues here.
One, as the other posters have said, you can't strictly do what you're looking to do (as you may be able to with C and the like). However - the behavior and intent are still readily workable in C# - you just have to do it the C# way.
The other issue is your unfortunate attempt to try and use strings - which are, as one of the other posters mentioned - immutable - and by definition get copied around.
So, having said that, your code can easily be converted to this, which I think does do what you want:
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);
}
}
回答2:
As others have pointed out, you cannot store a reference to a variable in a field in C#, or indeed, any CLR language.
Of course you can capture a reference to a class instance that contains a variable easily enough:
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);
}
}
Easy peasy.
回答3:
It sounds like what you're trying to do here is make a field a reference to another storage location. Essentially having a ref
field in the same way you have a ref
parameter. This is not possible in C#.
One of the main issues with doing this is that in order to be verifiable the CLR (and C#) must be able to prove the object containing the field won't live longer than the location it points to. This is typically impossible as objects live on the heap and a ref
can easily point into the stack. These two places have very different lifetime semantics (heap typically being longer than the stack) and hence a ref
between can't be proven to be valid.
回答4:
At this line:
this.parameter = parameter;
...you copy the method parameter to the class member parameter
. Then, in Init()
you are assigning the value "success", again, to the class member parameter
. In your Console.Writeline, then, you are writing the value of the method parameter, "failed", because you never actually modify the method parameter.
What you are trying to do - the way you are trying to do it - is not possible, I believe in C#. I wouldn't try passing a string
with the ref
modifier.
回答5:
As answered by JaredPar, you can't.
But the problem is partly that string is immutable. Change your parameter to be of class Basket { public string status; }
and your code would basically work. No need for the ref
keyword, just change parameter.status
.
And the other option is of course Console.WriteLine("After: {0}", e.parameter);
. Do wrap parameter in a (write-only) property.
回答6:
If you don't want to introduce another class like MyRef
or StringBuilder
because your string is already a property in an existing class you can use a Func
and Action
to achieve the result you are looking for.
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);
}
}
And if you want to simplify the calling side further (at the expense of some run-time) you can use a method like the one below to turn (some) getters into setters.
/// <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();
}