Export (0) Print
Expand All

Data Types (C# vs. Java)

This topic discusses some of the primary similarities and differences in how data is represented, allocated, and garbage-collected in Java and in C#.

The concept of a class as a compound data type with fields, methods, and events is similar in Java and C#. (Class inheritance is discussed separately in the topic entitled Inheritance and Derived Classes (C# vs Java).) C# introduces the concept of a struct as a stack-allocated compound data type that does not support inheritance. In most other respects, structs are very similar to classes. Structs provide a lightweight way of grouping together related fields and methods for use in tight loops and other scenarios where performance is critical.

C# enables you to create a destructor method that is called before instances of a class are garbage-collected. In Java, a finalize method can be used to contain code that cleans up resources before the object is garbage-collected. In C#, this function is performed by the class destructor. The destructor resembles a constructor with no arguments and a preceding tilde character (~). Destructors in C# are optional and are rarely needed.

C# provides all the data types that are available in Java, and adds support for unsigned numerals and a new 128-bit high-precision floating-point type.

For each primitive data type in Java, the core class library provides a wrapper class that represents it as a Java object. For example, the Int32 class wraps the int data type, and the Double class wraps the double data type.

On the other hand, all primitive data types in C# are objects in the System namespace. For each data type, a short name, or alias, is provided. For instance, int is the short name for System.Int32 and double is the short form of System.Double.

The list of C# data types and their aliases is provided in the following table. As you can see, the first eight of these correspond to the primitive types available in Java. Note, however, that Java's boolean is called bool in C#.

Short Name

.NET Class

Type

Width

Range (bits)

byte

Byte

Unsigned integer

8

0 to 255

sbyte

SByte

Signed integer

8

-128 to 127

int

Int32

Signed integer

32

-2,147,483,648 to 2,147,483,647

uint

UInt32

Unsigned integer

32

0 to 4294967295

short

Int16

Signed integer

16

-32,768 to 32,767

ushort

UInt16

Unsigned integer

16

0 to 65535

long

Int64

Signed integer

64

-9223372036854775808 to 9223372036854775807

ulong

UInt64

Unsigned integer

64

0 to 18446744073709551615

float

Single

Single-precision floating point type

32

-3.402823e38 to 3.402823e38

double

Double

Double-precision floating point type

64

-1.79769313486232e308 to 1.79769313486232e308

char

Char

A single Unicode character

16

Unicode symbols used in text

bool

Boolean

Logical Boolean type

8

True or false

object

Object

Base type of all other types

string

String

A sequence of characters

decimal

Decimal

Precise fractional or integral type that can represent decimal numbers with 29 significant digits

128

±1.0 × 10e−28 to ±7.9 × 10e28

Because C# represents all primitive data types as objects, it is possible to call an object method on a primitive data type. For example:

static void Main()
{
    int i = 10;
    object o = i;
    System.Console.WriteLine(o.ToString());
}    

This is achieved with the help of automatic boxing and unboxing. For more information, see Boxing and Unboxing (C# Programming Guide).

Both Java and C# provide the ability to declare a variable whose value is specified at compile time and cannot be changed at runtime. Java uses the final field modifier to declare such a variable, while C# uses the const keyword. In addition to const, C# provides the readonly keyword to declare variables that can be assigned a value once at runtime--either in the declaration statement or else in the constructor. After initialization, the value of a readonly variable cannot change. One scenario in which readonly variables are useful is when modules that have been compiled separately need to share data such as a version number. If module A is updated and recompiled with a new version number, module B can be initialized with that new constant value without having to be recompiled.

Enumerations, or enums, are used to group named constants similar to how they are used in C and C++; they are available in Java beginning in Version 1.5. In C#, enums are value types, and enum constants must be integral numeric values. The ToString method can be used to print out string representations of the named constants. The following example defines a simple Color enumeration in C#.

public enum Color
{
    Green,   //defaults to 0
    Orange,  //defaults to 1
    Red,     //defaults to 2
    Blue     //defaults to 3
}  

Integral values can also be assigned to enumerations as shown in the following enum declaration:

public enum Color2
{
    Green = 10,
    Orange = 20,
    Red = 30,
    Blue = 40
}

The following code example calls the GetNames method of the Enum type to display the available constants for an enumeration. It then assigns a value to an enumeration and displays the value.

class TestEnums
{
    static void Main()
    {
        System.Console.WriteLine("Possible color choices: ");

        //Enum.GetNames returns a string array of named constants for the enum. 
        foreach(string s in System.Enum.GetNames(typeof(Color)))
        {
            System.Console.WriteLine(s);
        }

        Color favorite = Color.Blue;

        System.Console.WriteLine("Favorite Color is {0}", favorite);
        System.Console.WriteLine("Favorite Color value is {0}", (int) favorite);
    }
}

Beginning in C# 3.0, enumerations can be extended with user-defined extension methods. For more information, see How to: Create a New Method for an Enumeration (C# Programming Guide).

Possible color choices:

Green

Orange

Red

Blue

Favorite Color is Blue

Favorite Color value is 3

String types in both Java and C# exhibit similar behavior with slight differences. Both string types are immutable, meaning that the values of the strings cannot be changed once the strings have been created. In both instances, methods that appear to modify the actual content of a string actually create a new string to return, leaving the original string unchanged. The process of comparing string values is different in C# and Java. To compare string values in Java, developers need to call the equals method on a string type as the == operator compares reference types by default. In C#, developers can use the == or != operators to compare string values directly. Even though a string is a reference type in C#, the == and != operator will, by default, compare the string values rather then references.

Just like in Java, C# developers should not use the string type for concatenating strings to avoid the overhead of creating new string classes every time the string is concatenated. Instead, developers can use the StringBuilder class, which is functionally equivalent to the Java StringBuffer class.

C# provides the ability to avoid the usage of escape sequences like "\t" for tab or "\" for backslash characters within string constants. To do this, simply declare the verbatim string using the @ symbol to precede the assignment of the string value. The following examples show how to use escape characters and how to assign string literals:

static void Main()
{
    //Using escaped characters: 
    string path1 = "\\\\FileShare\\Directory\\file.txt";
    System.Console.WriteLine(path1);

    //Using String Literals: 
    string path2 = @"\\FileShare\Directory\file.txt";
    System.Console.WriteLine(path2);
}

Both Java and C# follow similar rules for the automatic conversion and casting of data types.

Like Java, C# supports both implicit and explicit type conversions. In the case of widening conversions, the conversions are implicit. For example, the following conversion from int to long is implicit, as in Java:

int int1 = 5;
long long1 = int1;  //implicit conversion

The following is a list of implicit conversions between .NET Framework data types:

Source Type

Target Type

Byte

short, ushort, int, uint, long, ulong, float, double, or decimal

Sbyte

short, int, long, float, double, or decimal

Int

long, float, double, or decimal

Uint

long, ulong, float, double, or decimal

Short

int, long, float, double, or decimal

Ushort

int, uint, long, ulong, float, double, or decimal

Long

float, double, or decimal

Ulong

float, double, or decimal

Float

double

Char

ushort, int, uint, long, ulong, float, double, or decimal

You cast expressions that you want to explicitly convert using the same syntax as Java:

long long2 = 5483;
int int2 = (int)long2;  //explicit conversion

The following table lists explicit conversions.

Source Type

Target Type

Byte

sbyte or char

Sbyte

byte, ushort, uint, ulong, or char

Int

sbyte, byte, short, ushort, uint, ulong, or char

Uint

sbyte, byte, short, ushort, int, or char

Short

sbyte, byte, ushort, uint, ulong, or char

Ushort

sbyte, byte, short, or char

Long

sbyte, byte, short, ushort, int, uint, ulong, or char

Ulong

sbyte, byte, short, ushort, int, uint, long, or char

Float

sbyte, byte, short, ushort, int, uint, long, ulong, char, ordecimal

Double

sbyte, byte, short, ushort, int, uint, long, ulong, char, float, or decimal

Char

sbyte, byte, or short

Decimal

sbyte, byte, short, ushort, int, uint, long, ulong, char, float, or double

C# supports two kinds of variable types:

  • Value types

    These are the built-in primitive data types, such as char, int, and float, as well as user-defined types declared with struct.

  • Reference types

    Classes and other complex data types that are constructed from the primitive types. Variables of such types do not contain an instance of the type, but merely a reference to an instance.

If you create two value-type variables, i and j, as follows, then i and j are completely independent of each other:

int i = 10;
int j = 20;

They are given separate memory locations:

Separate memory addresses for value types

If you change the value of one of these variables, the other will naturally not be affected. For instance, if you have an expression such as the following, then there is still no connection between the variables:

int k = i;

That is, if you change the value of i, k will remain at the value that i had at the time of the assignment.

i = 30;

System.Console.WriteLine(i.ToString());  // 30
System.Console.WriteLine(k.ToString());  // 10

Reference types, however, act differently. For instance, you could declare two variables as follows:

Employee ee1 = new Employee();
Employee ee2 = ee1;

Now, because classes are reference types in C#, ee1 is known as a reference to Employee. The first of the previous two lines creates an instance of Employee in memory, and sets ee1 to reference it. Thus, when you set ee2 to equal ee1, it contains a duplicate of the reference to the class in memory. If you now change properties on ee2, properties on ee1 reflect these changes, because both point to the same object in memory, as shown in the following:

Memory locations for reference types

The process of converting a value type to a reference type is called boxing. The inverse process, converting a reference type to a value type, is called unboxing. This is illustrated in the following code:

int i = 123;      // a value type 
object o = i;     // boxing 
int j = (int)o;  // unboxing

Java requires you to perform such conversions manually. Primitive data types can be converted into objects of wrapper classes by constructing such objects, or boxing. Similarly, the values of primitive data types can be extracted from the objects of wrapper classes by calling an appropriate method on such objects, or unboxing. For more information about boxing and unboxing, see Boxing and Unboxing (C# Programming Guide).

Community Additions

ADD
Show:
© 2014 Microsoft