C# ref keyword usage
I understand (or at least I believe I do) what it means to pass an instance of a class to a method by ref
vers开发者_如何学Cus not passing by ref
. When or under what circumstances should one pass a class instance by ref
? Is there a best practice when it comes to using the ref
keyword for class instances?
The clearest explanation I've ever run across for output and ref parameters is ... Jon Skeet's.
Parameter Passing in C#
He doesn't go into "best practices", but if you understand the examples he's given, you'll know when you need to use them.
When you may replace the original object, you should send him as ref
. If it's just for output and can be uninitialized before calling the function, you'll use out
.
Put succinctly, you would pass a value as a ref
parameter if you want the function you're calling to be able to alter the value of that variable.
This is not the same as passing a reference type as a parameter to a function. In those cases, you're still passing by value, but the value is a reference. In the case of passing by ref
, then an actual reference to the variable is sent; essentially, you and the function you're calling "share" the same variable.
Consider the following:
public void Foo(ref int bar)
{
bar = 5;
}
...
int baz = 2;
Foo(ref baz);
In this case, the baz
variable has a value of 5, since it was passed by reference. The semantics are entirely clear for value types, but not as clear for reference types.
public class MyClass
{
public int PropName { get; set; }
}
public void Foo(MyClass bar)
{
bar.PropName = 5;
}
...
MyClass baz = new MyClass();
baz.PropName = 2;
Foo(baz);
As expected, baz.PropName
will be 5, since MyClass
is a reference type. But let's do this:
public void Foo(MyClass bar)
{
bar = new MyClass();
bar.PropName = 5;
}
With the same calling code, baz.PropName
will remain 2. This is because even though MyClass
is a reference type, Foo
has its own variable for bar
; bar
and baz
just start out with the same value, but once Foo
assigns a new value, they are just two different variables. If, however, we do this:
public void Foo(ref MyClass bar)
{
bar = new MyClass();
bar.PropName = 5;
}
...
MyClass baz = new MyClass();
baz.PropName = 2;
Foo(ref baz);
We'll end up with PropName
being 5, since we passed baz
by reference, making the two functions "share" the same variable.
The ref
keyword allows you to pass an argument by reference. For reference types this means that the actual reference to an object is passed (rather than a copy of that reference). For value types this means that a reference to the variable holding the value of that type is passed.
This is used for methods that need to return more than one result but don't return a complex type to encapsulate those results. It allows you to pass a reference to a object into the method so that the method can modify that object.
The important thing to remember is that reference types are not normally passed by reference, a copy of a reference is passed. This means that you are not working with the actual reference that was passed to you. When you use ref
on a class instance you are passing the actual reference itself so all modifications to it (like setting it to null
for example) will be applied to the original reference.
When passing reference types (non-value-types) to a method, only the reference is passed in both cases. But when you use the ref keyword, the method being called can change the reference.
For example:
public void MyMethod(ref MyClass obj)
{
obj = new MyClass();
}
elsewhere:
MyClass x = y; // y is an instance of MyClass
// x points to y
MyMethod(ref x);
// x points to a new instance of MyClass
when calling MyMethod(ref x), x will point to the newly created object after the method call. x no longer points to the original object.
Most use cases for passing a reference variable by reference involve initialization and out is more appropriate than ref. And they compile to the same thing (the compiler enforces different constraints - that ref variables be initialized before being passed in and that out variables are initialized in the method). So the only case I can think of where this would be useful is where you need to do some checking of an instantiated ref variable and may need to reinitialize under certain circumstances.
This might also be necessary to modify an immutable class (like string) as pointed out by Asaf R.
I found that it is easy to run into trouble using the ref keyword.
The following method will modify f even without the ref keyword in the method signature because f is a reference type:
public void TrySet(Foo f,string s)
{
f.Bar = s;
}
In this second case however, the original Foo is affected only by the first line of code, the rest of the method somehow creates and affects only a new local variable.
public void TryNew(Foo f, string s)
{
f.Bar = ""; //original f is modified
f = new Foo(); //new f is created
f.Bar = s; //new f is modified, no effect on original f
}
It would be good if the compiler gave you a warning in that case. Basically what you are doing is replacing the reference you received with another one referencing a different memory area.
It you actually want to replace the object with a new instance, use the ref keyword:
public void TryNew(ref Foo f, string s)...
But are you not shooting yourself in the foot? If the caller is not aware that a new object is created, the following code will probably not work as intended:
Foo f = SomeClass.AFoo;
TryNew(ref f, "some string"); //this will clear SomeClass.AFoo.Bar and then create a new distinct object
And if you try to "fix" the problem by adding the line:
SomeClass.AFoo = f;
If the code holds a references to SomeClass.AFoo somewhere else, that reference will become invalid...
As a general rule, you probably should avoid using the new keyword to alter an object which you read from another class or received as a parameter in a method.
Regarding the use of the ref keyword with reference types, I can suggest this approach:
1) Don't use it if simply setting the values of the reference type but be explicit in your function or parameter names and in the comments:
public void SetFoo(Foo fooToSet, string s)
{
fooToSet.Bar = s;
}
2) When there is a legitimate reason to replace the input parameter with a new, different instance, use a function with a return value instead:
public Foo TryNew(string s)
{
Foo f = new Foo();
f.Bar = s;
return f;
}
But using this function may still have unwanted consequences with the SomeClass.AFoo scenario:
SomeClass.AFoo = TryNew("some string");//stores a different object in SomeClass.AFoo
3) In some cases such as the string swapping example here it is handy to use ref params, but just as in case 2 make sure that swapping the object addresses does not affect the rest of your code.
Because it manages memory allocation for you, C# makes it all too easy to forget everything about memory management but it really helps to understand how pointers and references work. Otherwise you may introduce subtle bugs that are difficult to find.
Finally, this is typically the case where one would want to use a memcpy like function but there is no such thing in C# that I know of.
精彩评论