问题
I have looked at all the articles I could find in this site and there is a lot of opinion that at times is confusing or at odds. This is where my research in my own application has lead me. and I will try to state it as simply as possible. My concern is not with variables of simple types - I am only focusing on custom objects. I have verified this using VS 2017 and VS2019. Here are the essential basics to well managed implementation. All custom classes are derived from System.Object and are "Reference type". Therefore:
- No matter how you formulate the function or the parameters it is passed be reference. In the example below after the first call (ChangeSomething() both "ditto" and "org1" have the integer 1 in the integer list.
class MainClass
{
public class MyClass
{
public List<int> IntegerList{get;set;}
}
private void Actions
{
MyCLass obj1 = new MyClass();
MyClass ditto = new MyClass();
ditto = onj1;
ChangeSomething(ditto);
RefChange(obj1);
private void ChangeSomething(MyClass parm)
{
parm.IntegerList.Add(1);
}
private void RefChange(ref MyClass arg)
{
arg.IntegerList.Add(2);
}
}
}
Even if you pass it to a function with a reference parameter, as with the RefChange function both items are changed and both have exactly the same values in the integer list. Also, the ref marker is poor design. the ref keyword should generally be reserved for System.DerivedTypes
If you want to duplicate an object but have changes in one instance not affect the other, use a Copy Constructor. using the "=" sign does exactly that - makes the second object pointer point to the first object pointer.
If the above statements are true. What is the best way to create a fully independent duplicate of an object whose lifespan is within a single function? I am interested in Microsoft only and using current technology that targets desktops.
回答1:
Before you get into parameters, you need some background:
Background
There are two kinds of objects in .NET-land, Reference types and Value types. The main difference between the two is how assignment works.
Value Types
When you assign a value type instance to a variable, the value is copied to the variable. The basic numeric types (int
, float
, double
, etc) are all value types. As a result, in this code:
decimal dec1 = 5.44m;
decimal dec2 = dec1;
dec1 = 3.1415m;
both decimal variables (dec
and dec2
) are wide enough to hold a decimal valued number. In each case, the value is copied. At the end, dec1 == 3.145m
and dec2 == 5.44m
.
Nearly all value types are declared as a struct
(yes, if you get access to the .NET sources, int
is a struct
). Like all .NET types, they act as if they are derived from the object
base class (their derivation is through System.ValueType
. Both object
(aka System.Object
) and System.ValueType
are reference types, even though the types that derive from System.ValueType
are value types (a little magic happens here).
All value types are sealed/final - you can't sub-class them. You also can't create a default constructor for them - they come with a default constructor that initializes them to their default value. You can create additional constructors (which don't hide the built-in default constructor).
All enums
are value types as well. They inherit from System.Enum
but are value types and behave mostly like other value types.
In general, value types should be designed to be immutable; not all are.
Reference Types
Variables of reference types hold references, not values. That said, it sometimes help to think of them holding a value - it's just that that value is a reference to an object on the managed heap.
When you assign to a variable of reference type, you are assigning the reference. For example:
public class MyType {
public int TheValue { get; set; }
// more properties, fields, methods...
}
MyType mt1 = new MyType() {TheValue = 5};
MyType mt2 = mt1;
mt1.TheValue = 42;
Here, the mt1
and mt2
variables both contain references to the same object. When that object is mutated in the final line of code, you end up with two variables both referring to an object whose TheValue
property is 42.
All types declared as a class
are reference types. In general, other than the numeric types, enums
and bools
, most (but not all) of the types that you normally encounter will be reference types.
Anything declared to be a delegate
or an event
are also reference types under the covers. Someone mentioned interface
. There is no such thing as an object typed purely as an interface
. Both structs
and classes
may be declared to implement an interface
- it doesn't change their value/reference type nature.
Difference in Constructor Behavior
One other difference between Reference and Value Types is what the new
keyword means when constructing a new object. Consider this class and this struct:
public class CPoint {
public float X { get; set; }
public float Y { get; set; }
public CPoint (float x, float y) {
X = x;
Y = y;
}
}
public struct SPoint {
public float X { get; set; }
public float Y { get; set; }
public CPoint (float x, float y) {
X = x;
Y = y;
}
}
They are basically the same, except that CPoint
is a class
(a reference type) and SPoint
is a struct
(a value type).
When you create an instance of SPoint
using the two float constructor (remember, it gets a default constructor auto-magically), like this:
var sp = new SPoint (42.0, 3.14);
What happens is that the constructor runs and creates a value. That value is then copied into the sp
variable (which is of type SPoint
and large enough to hold a two-float SPoint
).
If I do this:
var cp = new CPoint (42.0, 3.14);
Something very different happens. First, memory is allocated on the managed heap large enough to hold a CPoint
(i.e., enough to hold two floats plus the overhead of the object being a reference type). Then the two-float constructor runs (and that constructor is the only constructor - there is no default constructor (the additional, programmer-written constructor hides the compiler generated default constructor)). The constructor initializes that newCPoint
in the memory allocated on the managed heap. Finally, a reference to that newly create object is created and copied to the variable cp
.
Parameter Passing
Sorry the preamble took so long.
Unless otherwise specified, all parameters to functions/methods are passed by value. But, don't forget that the value of a variable of reference type is a reference.
So, if I have a function declared as (MyType
is the class declared above):
public void MyFunction(decimal decValue, MyType myObject) {
// some code goes here
}
and some code that looks like:
decimal dec1 = 5.44m;
MyType mt1 = new MyType() {TheValue = 5};
MyFunction (dec1, mt1);
What happens is that the value of dec1
is copied to the function parameter (decValue
) and available for use within MyFunction
. If someone changes the value of the decValue
within the function, no side effects outside the function occurs.
Similarly, but differently, the value of mt1
is copied to the method parameter myObject
. However, that value is reference to a MyType
object residing on the managed heap. If, within the method, some code mutates that object (say: myObject.TheValue=666;
), then the object to which both the mt1
and myObject
variables refer is mutated, and that results in a side effect viewable outside of the function. That said, everything is still being passed by value.
Passing Parameters by Reference
You can pass parameters by reference in two ways, using either the out
or ref
keywords. An out
parameter does not need to be initialized before the function call (while a ref
parameter must be). Within the function, an out
parameter must be initialized before the function returns - ref
parameters may be initialized, but they do not need to be. The idea is that ref
parameters expect to pass in and out of the function (by reference). But out
parameters are designed simply as a way to pass something out of the function (by reference).
If I declare a function like:
public void MyByRefFunction(out decimal decValue, ref MyType myObject) {
decValue = 25.624; //decValue must be intialized - it's an out parameter
myObject = new MyType (){TheValue = myObject.TheValue + 2};
}
and then I call it this way
decimal dec1; //note that it's not initalized
MyType mt1 = new MyType() {TheValue = 5};
MyType mt2 = mt1;
MyByRefFunction (out dec1, ref mt1);
After that call, dec1
will contain the value 25.624
; that value was passed out of the function by reference.
Passing reference type variables by reference is more interesting. After the function call, mt1 will no longer refer to the object created with TheValue
equal to 5, it will refer to the newly created object with TheValue
equal to 5 + 2
(the object created within the function). Now, mt1
and mt2
will refer to different object with different TheValue
property values.
With reference types, when you pass a variable normally, the object you pass it may mutate (and that mutation is visible after the function returns). If you pass a reference by reference, the reference itself may mutate, and the value of the reference may be different after the function returns.
回答2:
All custom objects (derived from tobject) are "Reference type".
Nope. See the docs pages for Reference Types and Value Types
The following keywords are used to declare reference types:
class
interface
delegate
C# also provides the following built-in reference types:
dynamic
object
string
A value type can be one of the two following kinds:
- a structure type ...
- an enumeration type ...
So any time you make a class, it's always a Reference type.
EVERY type inherits from Object
- Value Types and Reference Types.
Even if you pass it to a function with a reference parameter, as with the RefChange function both items are changed and both have exactly the same values in the integer list.
The ref
keyword just forces your parameter to be passed by reference. Using ref
with a Reference Type allows you to reassign the original passed in reference. See What is the use of “ref” for reference-type variables in C#?
.
Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.
Source
Of course, the ref
keyword is important when you pass in a Value Type, such as a struct
.
If you want to pass a copy of an object, create an overloaded constructor to which you pass the original object and inside the constructor manage the duplication of the values that matter.
That's called a Copy Constructor, and is a long-established pattern, if you want to use it. In fact, there is a new c# 9.0 feature all about it: records.
回答3:
well i cant comment since my reputation is too low, but value types are usually in built types such as int, float ...
everything else is reference type. reference type is always a shallow copy regardless of ref keyword.
ref keyword mainly served for value-type or act as a safeguard.
if u want to deep copy, Icloneable is very useful.
来源:https://stackoverflow.com/questions/65418824/object-passing-reference-vs-value