-1

To give context to the code imagine a collection of objects inside a cube. The objects are placed randomly and can affect each other. Several series of test events are planned then executed against the cube of objects. Only the best result is kept. This is not the real problem but a simplified version to focus the question. Sample code

class Loc{
   double UpDown
   double LeftRight
   double FrontBack
}
class Affects{
    string affectKey
    List<string> impacts //scripts that execute against properties
}
class Item{
    Loc startLoc
    Loc endLoc
    List<string> affectedBy
    string resultText // summary of analysis of changes
}
class ItemColl{
    List<Item> myItems
}

class main{
    ItemColl items
    List<string> actions

    void ProcessAffects(ItemColl tgt, List<string> acts){
        // take actions against the tgt set and return 
    }
    int IsBetter(ItemColl orig, List<Items> altered){
        // compares the collection to determine "better one"
        // positive better, negative worse, zero for no change
    }
    void DoThings(){
        // original code
        ItemColl  temp = items
        ProcessAffects(temp,actions)
        IsBetter(temp,actions)
        // the result was always zero - admittedly a duh error
    }
}

When I added an alternate constructor that copied the object passed in and did the same to all subordinate objects, as in

class ItemColl{
    public ItemColl(){}
    public ItemColl (ItemColl clone){
        // do a deep copy
    }
    // partial code from main DoThings
    // replaced ItemColl temp = items 
    // with
        ItemColl temp = new ItemColl(items)

it solved the problem that lead me to first question. (Thanks to the people who answered that question kindly.) What I am stuck on is whether or not there are other options to consider? I am hoping this restatement has a better focus and if I am not taking advantage of some newer efficiencies I would like to know.

I removed the old question entirely and re-phrased post face-palm.

  • Fun fact, `int` inherits from `Object`, and `int` is a Value Type, not a Reference Type. The key you're looking for here is `class` vs `struct`. – gunr2171 Dec 23 '20 at 03:59
  • Keep in mind when you are passing an object as a function argument - in fact, you are passing a `pointer` to an address in memory and not the value itself. – Jonathan Applebaum Dec 23 '20 at 04:05
  • Thank you. Getting the words right matters. in the back of my head I try to trace it all back to the world of alloc/malloc and sometimes I can see the picture but not know the words to say it. – Paul Wichtendahl Dec 23 '20 at 04:05
  • jonathana, that is right but underlying my question really is what functionality does the "=" do when you create the second object. In some languages I have worked with that operator allocates all the memory and duplicates all the memory space allocated with the first instance underneath the second pointer. This is a "full" implementation of equal. .NET simply and efficiently seems to assign a pointer to the first instance of the object as the total value of the second. This is quick and memory efficient but it does have a price. – Paul Wichtendahl Dec 23 '20 at 04:11
  • #2 about `IDisposable` is wrong. If you're thinking about C++ destructors, stop. It's nothing like that. Don't try to treat them that way. It'll get you in trouble. – madreflection Dec 23 '20 at 04:16
  • 2
    There is a lot to unpack here, and i am not sure the question as it stands is suitable for stackoverflow and has little use to future users. – TheGeneral Dec 23 '20 at 04:21
  • 1
    @PaulWichtendahl yes when duplicating an object it has a price, as madreflection pointed out `IDisposiable` will not "clean" it from memory" that is why .NET has a garbage collector, keep in mind that if the duplicated object is a function local variable the garbage collector will give it a high priority for collecting it from memory when the local variable is no longer used – Jonathan Applebaum Dec 23 '20 at 04:24
  • *"what functionality does the "=" do"*? Copy the variable. That's it. A reference type's variable is the pointer. A value type's variable is the contents (layout) of the struct. `=` goes no deeper than that, and it's not a candidate for overloading so it never will do more than that. In terms of the `new` expression, the allocated object's reference is copied to the variable on the left of the `=`. – madreflection Dec 23 '20 at 04:27
  • @jonathana using IDisposible does not clear it from memory but it has always been important for QUALITY memory management in an application to explicitly dismiss objects when you are done with them. This has to do with how an applications stack space interacts with the core memory manager. Good programmers manage their environment - sloppy or incomplete programs just "let the framework do it". Also, I have seen instances where GC got too aggressive and cleaned up something before it was no longer referenced. Implementing IDispose stops that. – Paul Wichtendahl Dec 23 '20 at 05:19
  • 1
    @madreflection You are right again. What is nice is that you stated it very succinctly. What was wrong in my head goes right down to this. It was doing EXACTLY what you said. The trick is understanding that a class is just a pointer as far as the functional execution is concerned so when you use the equal you are saying "pointer b is now equal to pointer a" then the higher level behavior makes perfect sense. So much naturally follows that truth and somehow it simply didn't hold in the grey matter. Ah the difference between understanding what you are doing vs a recipe for how to do it. – Paul Wichtendahl Dec 23 '20 at 05:27
  • Glad to here that. Hopefully I can stay on a roll here... `IDisposable` is not about memory management. You're thinking about how C++ works and that's not at all how it works in .NET. Unless the program terminates, deallocation of objects on the managed heap is performed by the garbage collector. `IDisposable` doesn't change that. `IDisposable` is only there to deallocated *unmanaged* resources. Also, a Finalizer only uses destructor syntax, but that's as far as the similarity goes. Take at look at my answer [here](https://stackoverflow.com/a/54361746/5394572). – madreflection Dec 23 '20 at 05:32
  • First - Nice post. I used to argue against IDisposable because it was implemented incompletely every time I saw it. Then I became a believer. Now, well you quoted Raymond Chen so authority is on your side in my eyes FWIW. I would really have to use some form of memory spy to authenticate it but I have so many other algorithms to move into this decade I think I am back to coding. Converting a rather large library to a common code base is either noble or a vanity - either way it wears a body out. Back to coding. @TheGeneral may debate the value of the post but I have learned alot, – Paul Wichtendahl Dec 23 '20 at 05:45
  • 1
    `There is a lot to unpack here, and i am not sure the question as it stands is suitable for stackoverflow and has little use to future users.` I agree, mainly due to https://meta.stackexchange.com/questions/39223/one-post-with-multiple-questions-or-multiple-posts . There are lots of incorrect statements / assertions in the question and so many questions - way too broad. – mjwills Dec 23 '20 at 06:22
  • I suspect https://stackoverflow.com/questions/971170/using-ref-with-class-c-sharp is the main duplicate you should read. – mjwills Dec 23 '20 at 06:25

3 Answers3

2

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 when boxed 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 unboxed 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, but a struct stored as an interface will be boxed.

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.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
Flydog57
  • 6,851
  • 2
  • 17
  • 18
  • `Nearly all value types are declared as a struct (yes, if you get access to the .NET sources, int is a struct)` Which value type _isn't_ a struct? – mjwills Dec 23 '20 at 06:24
  • @mjwills: `public enum Stuff { one, two, red };` and `var (a, b, c) = (12, "yes", 3.14);`. The latter is struct-ish – Flydog57 Dec 23 '20 at 06:34
  • Pedantic, but I'll pay that. ;) – mjwills Dec 23 '20 at 06:40
  • The Tuple may be pedantic. But the enum isn't. Structs all inherit directly from System.ValueType. Enums inherit from System.Enum (which may inherit from System.ValueType - I can't remember). Though they are value types, there's nothing really struct-ish about them. They are more their own type of type than Delegates are (at least in my head) – Flydog57 Dec 23 '20 at 06:44
  • `there's nothing really struct-ish about them` Other than the fact that they fulfil the `struct` requirement for generics (mind you, a struct can be a `class` according to generics - so generics are weird), have struct copy behaviour etc etc. ;P But yes, you make a valid point. I suppose I have always thought of `struct` as synonymous for `value type`, and you have made it slightly less black and white. ;) – mjwills Dec 23 '20 at 06:47
  • Technically, "even though the types that derive from System.ValueType are value types (a little magic happens here)." is not true. [ECMA-335 states](https://www.ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf) in **I.8.9.10** that the *boxed* version of the struct inherits, the unboxed does not, and unboxed structs are what we use in C#. – Charlieface Feb 13 '21 at 20:31
1

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.

gunr2171
  • 16,104
  • 25
  • 61
  • 88
  • *"it doesn't make a difference"* except that the callee can modify the variable in the caller's scope. – madreflection Dec 23 '20 at 04:14
  • @madreflection isn't that the same as if you pass in a reference type and don't provide `ref`? – gunr2171 Dec 23 '20 at 04:15
  • 1
    No. You can modify the object to which it refers, but not the local itself. `ref` allows you to change the local. It's a reference to the stack location that holds the reference to the managed heap.' – madreflection Dec 23 '20 at 04:16
  • Huh, well... I learned something. – gunr2171 Dec 23 '20 at 04:16
  • That's why `ref` on a value type is useful, especially if you have a large one. `ref Guid g` will give a reference to the stack space that holds the Guid. Without `ref`, it will copy the whole Guid (all 16 bytes) onto the stack. – madreflection Dec 23 '20 at 04:18
  • Following on that, `in Guid g` still passes it by reference but by declaring it that way, the method is saying, "but I won't modify what it refers to". – madreflection Dec 23 '20 at 04:20
  • Yes, and with the `int` example, if the method modifies the parameter, it gets changed back from the caller. I made a bad assumption about ref with reference types - shows how much I use them. – gunr2171 Dec 23 '20 at 04:21
  • 1
    Yeah, `ref` with reference types is usually a sign that the design is flawed. – madreflection Dec 23 '20 at 04:22
  • Thank you everyone! THis is the discussion I was hoping for. – Paul Wichtendahl Dec 23 '20 at 04:40
  • `interface` has nothing to do with reference/value type identity. Both `classes` and `structs` can be declared to implement an `interface` – Flydog57 Dec 23 '20 at 05:07
  • gunr2171 - Also your statement about reference types and value types is misleading to my point. I would invite you to look [here] (https://www.infoworld.com/article/3043992/a-deep-dive-value-and-reference-types-in-net.html). The site is annoying but the article is written rather well. Reference Type all inherit from System.Object or what us oldsters use to call TObject.of which Class is one of the most primitive inheritors. That is my bad - first word vs right word. All reference types inherit from [System.Object](https://docs.microsoft.com/en-us/dotnet/api/system.object?view=net-5.0) – Paul Wichtendahl Dec 23 '20 at 05:11
  • That article over-simplifies things, although it properly *alludes* to the fact that the stack is an implementation detail, since value types can be optimized to registers, or an implementation may choose to allocate them on the managed heap just the same (although none actually does, that's ridiculous). Frankly, gunr2171 and Flydog57 have done a far better job of breaking it down and drawing very precise distinctions. – madreflection Dec 23 '20 at 05:23
  • @paulwichtendahl: .NET has never had a type named `TObject` and `Class` has never been a system-supplied class (though `Type` is a type). And. Madreflection, consider a `class` that can be initialized by parsing a string. In that case, a `static` method like `MyClass.TryParse(string string To Parse, out MyClass myObject)` would be a very good design – Flydog57 Dec 23 '20 at 05:29
  • @Flydog57: Agreed, and I thought of it immediately after posting it but I figured that would count as an exception because it's such an established pattern. Also, that's an `out` parameter and, while the difference is razor thin, it's a distinction in C# nonetheless. – madreflection Dec 23 '20 at 05:34
  • @Flydog57 you are right. However there was programming before .NET and I regrettably have in my head a lexicon of words that no longer have meaning. TObject was the Borland Delphi equivalent of ATL Object which was the pre-cursor to System.Object. You are right, in my head I was right, unfortunately the words are messed up in the original post – Paul Wichtendahl Dec 23 '20 at 05:35
-1

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.

Edwin Chew
  • 49
  • 1
  • 4