I am manually converting code from Java to C# and struggling with (what I call) primitive types (see, e.g. Do autoboxing and unboxing behave differently in Java and C#). From the answers I understand that double
(C#) and Double
(C#) are equivalent and double
(C#) can also be used in containers, e.g. as a key in a Dictionary. However, double
(Java) cannot be used in containers like HashMap which is why it is auto-boxed to Double
(Java).
double
(C#) a primitive or an object? double
(Java)?double
(C#) cannot be set to null unless it is made nullable
.
double?
(C#) equivalent to Double
(Java)? Are they both referred to as objects? (Is the use of the term "first-class object" useful in this discussion?)
Both C# and Java have primitive (or "value") types: int, double, float, etc...
However, after this C# and Java tend to divide.
Java has wrapper Class types for all of the primitive types (which is a small finite set in Java) which allows them to be treated as Object.
double/Double
, int/Integer
, bool/Boolean
, etc. These wrapper types are reference-types (read: Classes) and, as such, null
is a valid value to assign to such typed expressions/variables. Recent versions of Java (1.5/5+) add in implicit coercions from primitives to their corresponding wrapper.
// Java
Boolean b = true; // implicit conversion boolean -> Boolean (Java 5+)
Boolean b = null; // okay, can assign null to a reference type
boolean n = null; // WRONG - null is not a boolean!
C# doesn't provide a such a direct wrapping1 - in part, because C# supports an infinite set of value types via structures; rather, C# handles "nullable value types" by introduction of a Nullable<T>
wrapper type. In addition C#, like Java, has implicit conversions from the value type T
to Nullable<T>
, with the restriction that T is "not a nullable type" itself.
// C#
Nullable<bool> b = true; // implicit conversion bool -> bool?
bool? b = true; // short type syntax, implicit conversion
bool? b = null; // okay, can assign null as a Nullable-type
bool b = null; // WRONG - null is not a bool
Note that Nullable<T>
is also a value type and thus follows the standard structure rules for when/if a value is "on the stack" or not.
In response to the comment:
Absolutely correct, Nullable being a value-type does allow it to have a more compact memory footprint in certain cases as it can avoid the memory overhead of a reference type: What is the memory footprint of a Nullable<T>. However it still requires more memory than the non-Nullable type because it has to remember if the value is, well, null, or not. Depending upon alignment issues and VM implementation, this may or may not be significantly less than a "full" object. Also, since values in C#/CLR are reified, consider any lifting operations that must be performed:
// C#
object x = null;
x = (bool?)true;
(x as bool?).Value // true
The article Java Tip 130: Do you know your data size? talks about reference type memory consumption (in Java). One thing to note is that the JVM has specialized versions of Arrays internally, one for each primitive type and for Objects (however, please note that this article contains some misleading statements). Note how the Objects (vs. primitives) incur extra memory overhead and the byte alignment issues. C# however, can extend the optimized-array case for Nullable<T>
types vs. the the limited special-cases the JVM has because Nullable<T>
is itself just a structure type (or "primitive").
However, an Object, only requires a small fixed size to maintain a "reference" to it in a variable slot. A variable slot of type Nullable<LargeStruct>
on the other hand, must have space for LargeStruct+Nullable
(the slot itself may be on the heap). See C# Concepts: Value vs Reference Types. Note how in the "lifting" example above the variable is of type object
: object
is the "root type" in C# (parent of both reference types and value types) and not a specialized value type.
1 The C# language supports a fixed set of aliases for primitive/common types that allow access to "friendly lowercase" type names. For instance, double
is an alias for System.Double
and int
is an alias for System.Int32
. Unless a different Double
type is imported in scope, double
and Double
will refer to the same type in C#. I recommend using the aliases unless there is a reason to do otherwise.