Some questions about null reference checking by the CLR in C#
Consider this:
class CS
{
public int X;
}
void Foo(CS c)
{
c.X += 10;
}
CS c = default(CS);
Foo(c);
obviously a NullReferenceException is thrown here.
Now CS becomes a struct
struct CS { public int X; }
CS c = default(CS);
Foo(c);
Now no exception is thrown
Then Foo is changed into taking a ref CS cs argument.
void Foo(ref CS c) { c.X += 10; }
Again no exception since CS is still a struct.
Now the questions.
Does the CLR generate an "if (x == null)" statement for every field reference of class objects or how does it do that?
Does the CLR do the same for ref arguments when the argument is a struct?
Is it possible to get null for the CS argument when CS is a struct?
Structs are value types, so they aren't null. (You'd have to make it a Nullable<CS> since value types can't be null - see Nullable docs)
default(CS) is a struct where X (another value type) is 0 (or more precisely, default(int)). All Fields in a struct are initialized to default(FieldType).
For more information about structs, check out the documentation.
Answers to your questions:
The act of dereferencing a reference to get to a member will throw the exception. No explicit or * implicit* if-statement is added but the effect is the same.
No, it does not. A struct variable can never be null. The compiler knows this.
No, a ref struct X parameter can never be null, you're not referencing the struct you're referencing the variable holding the struct. You can't compile code that references no variable when calling that method, hence no need.
If you were to use pointers, then yes, nulls can occur even with pointers to structs, but not with ref parameters.
Because struct in C# is a value-type, it cannot be null. You can use Nullable<YourStruct> and then use HasValue property to check if it's just initialized.
And a struct is always initialized. if not by you, then by the compiler with default values.
Related
This question already has answers here:
Why can I not modify the result of an unboxing conversion?
(3 answers)
Closed 7 years ago.
Let's look at the following code.
struct SPoint
{
public int x;
public int y;
public SPoint(int x, int y)
{
this.x = x;
this.y = y;
}
}
class Test
{
public static void Main()
{
SPoint s = new SPoint(3, 4);
object o = s;
((SPoint) o).x = 5;
}
}
Why isn't the last assignment possible? What is the reason for such behaviour?
Since s is a struct (a.k.a: a value type), (SPoint)o is a copy of the data:
From the C# language spec (§1.3, "Types and Variables"):
When a value of a value type is converted to type object, an object instance, also called a “box,” is allocated to hold the value, and the value is copied into that box. Conversely, when an object reference is cast to a value type, a check is made that the referenced object is a box of the correct value type, and, if the check succeeds, the value in the box is copied out.
The language protects you from changing the data of the cloned value type without putting it in a variable first, since you might think that you are changing the original s.x value while you are changing it's temporary (not - variabled) clone, unlike unsafe languages like C++/CLI where this kind of assignments might be allowed.
If you want, you are able to explicitly create a new variable and do your manipulations in it:
SPoint cloneOfS = ((SPoint)o);
cloneOfS.x = 5;
See MSDN:
The result of an unboxing conversion is a temporary variable. The compiler prevents you from modifying such variables because any modification would go away when the temporary variable goes away. To fix this, declare a new value-type variable to store the intermediate expression, and assign the result of the unboxing conversion to that variable.
In Visual Studio 2012 i have this simple example here:
and when i debug the code and move my cursor over i to get it's current value, i expect something like
Use of unassigned local variable
but there is a 0 which was not set - why is there a 0?
When you declare any local/block variable, they didn’t get the default values. They must assigned some value before accessing it other wise compiler will throw an error. If the variable has a global scope then default value can be assigned and accessed. if the variable is of reference type then the default value will be null. this link contains default values for the primitive datatype:
The compiler will not permit this(since it is local/block variable):
public static void samplemethod()
{
int a;
int b = a;
}
where as the following code works fine since the variable have global scope:
public int i;
public void samplemethod()
{
int a;
int b = i;
}
That's because int is a Value Type not a Reference Type.
MSDN :
Variables that are based on value types directly contain values.
Assigning one value type variable to another copies the contained
value. This differs from the assignment of reference type variables,
which copies a reference to the object but not the object itself.
Have a look at Value Types and Reference Types.
I hope it turns out to be helpful.
I started my journey with C# but I realised that I have some problems with some basic information about memory when it comes to declaration of variables. See if I am correct.
int x; // I declared variable of type int, which name is x. Compiler will provide memory for it but we dont have known value of it.
x=10; // Now memory location is still the same but value now kept there is 10;
public struct Point {
public int x, y;
}
Now I define a struct named Point. Beacuse struct is a value type, it again has reserved memory for it on the computer. Howewer x and y have no value.
Now Point p1 = new Point(); // what is happening here? Struct is not a reference type. So is this just initialization of Point variable with the default constructor without assigning values to x and y?
Second short question. When I write a code like:
int x = 10;
Can I say that I created instance of class integer which value is 10 and name x;
I would be grateful for help.
// what is happening here? Struct is not a reference type. So is this just initialization of Point variable with the default constructor without assigning values to x and y?
No; there are 4 possible scenarios here:
a class: the memory space is wiped to all 0s, then any custom constructor is invoked, which may also involve field initializers
a struct called without a custom constructor: the memory space is wiped to all 0s
a struct called with a custom constructor: the custom constructor is required to assign all the fields
a struct variable used without ever calling a constructor: this is actually a thing, but the calling code must write to all the fields before they can do anything else with it; since most structs do not expose their fields, this rarely works
Second short question. When i write a code like:
int x = 10;
Can i say that i created instance of class integer which value is 10 and name x; I would be grateful for help.
Not really, because in C# terms, int is not a class (it might be in IL terms). Simply: you have declared a local variable of type int with name x and assigned it the value 10, if this is in a method. If this is a class field, then: you have declared a private instance field of type int named x with a field-initializer giving it the value of 10.
Incidentally, you should avoid public fields in general, and mutable fields on structs. You might prefer:
public struct Point {
private readonly int x, y;
public int X { get { return x; } }
public int Y { get { return y; } }
public Point(int x, int y) { this.x = x; this.y = y'; }
}
This will avoid a huge range of problems.
In C# the default struct constructor sets the struct memory to 0, effectively setting all variables to their default values.
In case of ints, it will be 0. For reference types, it will result in null.
(in other words, for any type T it will be default(T)).
Note that when you write a custom constructor in a struct, you must initialize all member fields.
When you write
int x;
this is similar to
Point p1 = new Point(); (considering Point structure is already defined)
in both the cases all integer variables will have default value of 0 and not null, which is is basically what is used in C# to denote 'nothing' and can be assigned only to reference types.
As well, in c# everything is a class, so when you write
int x = 10;
you are creating an instance of class Int32, though the run time will handle this as value type instead of ref type, as special case.
Same is true for other basic types like, Long, DateTime and few others
classes deal with the reference types and traditional data types deal with the value type just for example :
int i=5;
int j=i;
i=3 ; //then this will output i=3 and j=5 because they are in the different memory blocks .
Similarly if we talk about the object of a class say point class
class point
{
public int x,y;
void somefucnt(point p,int x)
{
Console.writeline("value of x is "+p.x);
x=22;
Console.writeline("value of x is "+p.x);
}
}
class someotherclass
{
static void Main(string [] args )
{
p1.x=10;
p1.somefunct(p1,p1.x);
}
}
Both console.write statements are printing 10 , despite ive changed x to some other value ? why is it so ?since p is just the reference to x so it should be updated by changing values of x . this thing is really confusing me alot .
The observed behavior has nothing to do with Value types vs Reference types - it has to do with the Evaluation of Strategy (or "calling conventions") when invoking a method.
Without ref/out, C# is always Call by Value1, which means re-assignments to parameters do not affect the caller bindings. As such, the re-assignment to the x parameter is independent of the argument value (or source of such value) - it doesn't matter if it's a Value type or a Reference type.
See Reference type still needs pass by ref? (on why caller does not see parameter re-assignment):
Everything is passed by value in C#. However, when you pass a reference type, the reference itself is being passed by value, i.e., a copy of the original reference is passed. So, you can change the state of object that the reference copy points to, but if you assign a new value to the reference [parameter] you are only changing what the [local variable] copy points to, not the original reference [in the argument expression].
And Passing reference type in C# (on why ref is not needed to mutate Reference types)
I.e. the address of the object is passed by value, but the address to the object and the object is the same. So when you call your method, the VM copies the reference; you're just changing a copy.
1 For references types, the phrasing "Call By Value [of the Reference]" or "Call by [Reference] Value" may help clear up the issue. Eric Lippert has written a popular article The Truth about Value Types which encourages treating reference values as a distinct concept from References (or instances of Reference types).
void somefucnt(point p,int x){
Console.writeline("value of x is "+p.x);
x=22;
Console.writeline("value of x is "+p.x);
}
Here, the x=22 won´t change p.x but the parameter x of (point p,int x)
Normally, your assumtion about values/references is ok (if I understood it correctly).
Tip: Google for c# this instead of passing a object to it´s own method
You change the value of the parameter (x), not the value of p.x, value types are passed by value unless you use the ref keyword.
Like in your first example, there is no relationship between i and j as well as the parameter x, and p1.x.Each variable has it's own space in the memory.So changing one of them doesn't affect to the other.
You have two different variables named x in the somefucnt function. One is the member variable x which you are trying to change, the other is the function input parameter in void somefucnt(point p, int x). When you say x = 22, the input parameter x is changed instead of the member variable x.
If you change the line x = 22 to this.x = 22 then it should work as you expect.
Side note:
A good practice to avoid confusion is to always have class members private and name them as _x. Otherwise, have public auto properties in CamelCase, like this:
public int X { get; set; }
These methods avoid ambiguity between class variables and function input variables.
I have a code which works and compiles perfectly, where I have an Action defined as:
Action<double, double, double ....> OnBlah;
Now I have more than 16 params, so I want to pass a struct instead, so I defined the action as:
Action<structName> OnBlah;
But I get an error in the C# compiler that says that structName is not initialized. This is weird since:
A. Previously in the code I passed the double parameters for OnBlah from the struct directly.
B. Structs are initialized by definition.
I'm using VS 2010, I'm guessing this is a compiler bug ?
Added reproduction code for the compiler bug:
namespace CompilerBug
{
struct Test
{
public double a;
}
class Program
{
static Action<Test> OnBlah;
static void DoesCompileOk()
{
Test test;
test.a = 5;
OnBlah(test);
}
static void DoesNotCompileOk()
{
Test test;
int b = 0;
if (b == 4)
{
test.a = 5;
}
OnBlah(test);
}
static void Main(string[] args)
{
OnBlah = (t) => { };
}
}
}
This is not a compiler bug. You need to initialize the struct first, like the compiler says. Presumably your error is actually in the code which invokes the delegate since nothing else makes sense.
When you were invoking the delegate with your 16 parameter double version, the double parameters were initialized. If you had not initialized them, then the compiler would have emitted the same error. For example:
private static void actDouble(double d)
{
}
static void Main(string[] args)
{
double d;
Action<double> act = actDouble;
act(d);//error on this line
}
This fails to compile with the following error:
error CS0165: Use of unassigned local variable 'd'
There is nothing special about structs in this regard.
As a general rule, your first guess when you encounter a syntax error should never be that this is a compiler bug. Compiler bugs are exceedingly rare, especially in a compiler as widely used as this one.
Update following the edit to the question which added real code
Test test;
int b = 0;
if (b == 4)
{
test.a = 5;
}
OnBlah(test);
The compiler is not sure that test is fully initialized. It does not do program flow analysis. You can fix the problem by making sure that the compiler knows that the struct is fully initialized.
Action<structName> OnBlah;
can never raise the error you cite; if either of the two types Action or structName can't be resolved, it'll complain - and if the generic type argument isn't suitable it'll complain, but that is expected.
If OnBlah is a field it is automatically initialized to null; if it is a variable it will need to be assigned before use. It could be assigned to null, but if so it will fail at runtime when you try to invoke it (unless you do a null-check). So then we come to invoke; if we assume non-null, you need to supply an argument, i.e.
OnBlah(someValue); // or OnBlah.Invoke(someValue);
here, if someValue has not been defined it will error; it if is not definitely assigned it will error. For definite assignment, fields are assigned; variables are not unless initialized.
In the case of structs, a value is initialized either by using the constructor (or another method that returns the value), or by manually setting every field (which shouldn't be possible, since a: structs should be immutable, and b: fields should be private).