C# vs. Java: What Are the Main Differences?
I’m often asked, “What is the difference between C# and Java?” Or people ask for advice on which language to choose. If you’re asking that question, then the answer is probably “learn both.” But obviously, you must start with one of them.
In this blog post, I will compare the two languages from a coding point of view. In general, there aren’t that many important factors to choose between them. Android and Unity, for example, will choose for you: Java for Android development, C# for Unity game development.
Beyond considerations like that, the choice will probably depend on where you work. Most of the Fortune 500 companies will probably want Java, for example.

Until a few years ago, C# could only run on Windows, whereas Java could run on just about any operating system. C# uses the dot net framework, which was originally only available on the Windows operating system. In 2016, Microsoft produced .NET Core, making their C# language available on Linux. Microsoft’s .NET Core is free, open-source, and will run on Windows (obviously), Linux, and Mac OSX.
The two languages have more in common than they have differences. That’s good news because it means that it is much easier to learn both. Once you’ve mastered one of these languages, you’ll be able to get up to speed in the other very quickly.
In this post, we’ll look at the main language differences from a coding point of view.
Last Updated October 2022
Obtain C# Programming Language Skills With This C# Tutorial. Acquire Essentials Skills To Get a C# Developer Job Today. | By Tim Buchalka, Jean-Paul Roberts, Tim Buchalka’s Learn Programming Academy
Explore CoursePointers, call by reference, and unsafe code
Probably the most significant difference between C# and Java is that C# lets you use pointers. The Java runtime uses pointers. You might have seen a NullPointer exception when your code attempted to use an object that you hadn’t initialized. But pointers aren’t available for you to use in your Java code.
C# does allow you to use pointers. But if you do, you’re on your own. To see what that means, let’s consider the similarities in executing C# and Java code.
When you compile your Java code, the Java Virtual Machine (the JVM) executes the bytecode. Java doesn’t compile to machine code. Instead, the JVM interprets the bytecode and performs the necessary instructions.
The situation is the same with C#. The compiler translates C# code into an Intermediate Language (IL) that the Common Language Runtime (CLR) executes. Microsoft made C# available on other operating systems by including CLR as part of .NET Core.
So, both C# and Java code executes similarly. The compiler produces code into something that an interpreter can execute. They have different names for things: JVM vs CLR, bytecode vs IL, but the basic principles are the same.
Also, in both cases, the runtime takes care of memory management for us. We don’t have to worry about allocating memory for objects, nor about freeing it up again when we’ve finished with it. The memory gets returned by a process known as garbage collection.
But that only works if the runtime is aware of and controls the objects that we’re using.
C# lets us use pointers, which means we can access memory directly.
If we want to do that, we must use the unsafe keyword. Once you start using pointers, you lose the safety that C# provides with managed code. As I said, you’re on your own, and it becomes your responsibility to make sure the code is safe.
Here’s an example of a simple bubble sort, in C#. The code is so similar to Java that I haven’t provided a Java version — the comments show how to change it to Java.
using System;
// import in Java (although Java doesn't need to import System).
namespace BubbleSort
// package <packagename>; in Java. Goes before any imports.
{
class Program
{
public static void Main(String[] args) {
// Java uses lowercase m for main
int[] numbers = {3, 2, 4, 1, 5};
Sort(numbers);
foreach (int num in numbers) {
// Java -> for (int num : numbers) {
Console.Write(num + " ");
// Java -> System.out.print(num + " ");
}
}
static void Sort(int[] list) {
// sort in Java, but that's a convention not a language requirement.
bool stillSwapping = true; // C# bool -> Java boolean
while (stillSwapping) {
stillSwapping = false;
for (int i = 0; i < list.Length - 1; i++) {
// C# .Length -> Java .length (lowercase l).
if (list[i] > list[i + 1]) {
SwapValues(list, i, i + 1);
stillSwapping = true;
}
}
}
}
static void SwapValues(int[] list, int x, int y) {
// Would be swapValues (lowercase s) in Java
int temp = list[x];
list[x] = list[y];
list[y] = temp;
}
}
}
As you can see, the C# and Java code are very similar. There are only seven changes needed, and your IDE will probably take care of the first three of those.
I’ve added the SwapValues method, starting on line 29, because I want to talk about passing arguments by reference. Reference parameters are something that C# has that’s not possible in Java. In practice, you’d probably use
Array.Reverse(list, i, 2);
on line 22. But if we wanted to swap non-adjacent items in an Array, then we probably would write a method like SwapValues.
The way I’ve written it there, with the list and the two index positions as parameters, is how you’d have to do it in Java. The method then swaps the two items in the list.
In C#, we can use reference parameters instead. In both languages, we pass value types (integers, for example) by value. That means the function receives the value and the function can’t change the argument variable.
Using ref, in C#, we can pass the arguments by reference. Doing that allows the function to modify the original variables.
We can take advantage of that, to avoid having to pass the list to SwapValues. The method becomes:
static void SwapValues(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
and we call the function, on line 22, with:
SwapValues(ref list[i], ref list[i + 1]);
Inside the method, x and y are effectively pointers to the original arguments. Any changes made to x or y will also change the arguments — the two items in the list, in this case. That’s not possible in Java — value types can only pass their value. If we want to modify the list, we must pass the list to the method.
I said they’re “effectively” pointers. They’re not really pointers, of course, they’re ints. But you’ll understand why I mentioned pointers when we look at a version of this code that does use pointers.
Once again, this is something that’s not possible in Java. Using pointers, I mean. Obviously, writing a bubble sort is possible, because we’ve just seen it.
Here’s a C# version of the above methods, written using pointers:
static unsafe void Sort(int[] list) {
bool stillSwapping = true;
fixed(int* listStart = &list[0]) // get pointer to first element in the list.
{
int* listEnd = listStart + list.Length - 1;
while (stillSwapping) {
stillSwapping = false;
for (int* ptr = listStart; ptr < listEnd; ptr++) {
int* nextPtr = ptr + 1;
if (*ptr > *nextPtr) {
SwapValues(ptr, nextPtr);
stillSwapping = true;
}
}
}
}
}
static unsafe void SwapValues(int* p1, int* p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
There’s something seriously wrong with this, and I’ll talk about that in a moment. First, a quick explanation of the code.
If you want to use pointers and lose the safety of managed code, you must mark your method or code block as unsafe. Both methods include the unsafe keyword in their declaration.
Line 18 defines an int pointer. The syntax for declaring a pointer variable is to put an asterisk after the type: int* in this case. The code then assigns the address of the list, to the pointer. You get an address by using an ampersand before the object whose address you want.
Notice that we must use fixed. That prevents C# from moving the Array in memory. We’ve just created a pointer to it, and if the object we’re pointing to moves, our pointer address won’t continue to point to it. You must use fixed for any objects that your code will access via a pointer.
Line 25 shows how to dereference a pointer. The variable *ptr gives us the value that ptr is pointing to — the value in the Array, in this case. Similarly, for *nextPtr.
The code is very similar to the original code, but we’re using pointers to access the Array items rather than index positions.
The SwapValues method also dereferences the pointers that we pass as arguments. That code is very similar to the SwapValues method that used ref parameters. The ref parameters give us the same ability but in a safe context.
Avoid pointers
In case you’re thinking “Wow! C# has pointers, I’m definitely going to use C# rather than Java,” please don’t base any decision on that alone.
There are very few good reasons for using pointers in C#.
C# lets you use COM objects, or call functions in the Windows API, using InteropServices and the Marshall class. There’s very rarely a need to write unsafe code to use pointers to pass to API functions.
You might decide to use pointers for performance. The pointer version of our bubble sort would indeed perform faster than the safe version. But don’t be tempted to convert everything to unsafe code if performance is a problem. I said that there’s something seriously wrong with our pointer code, and there is. The problem is that it uses pointers to improve the performance of an inherently slow algorithm.
Rather than writing the code using pointers, we’d get far better performance by using a faster sorting algorithm. Heap Sort, Insertion Sort, and Quick Sort would all perform significantly faster than our bubble sort. Only if something like Quick Sort still wasn’t fast enough, should you consider using pointers to improve performance.
Eric Lippert addresses these points. You can see his answer here. Eric was one of the designers of the C# language, so he knows what he’s talking about!
The other thing wrong with that code is that it’s all unnecessary. The C# Array class and the Java Arrays class both have a Sort method (lowercase s for Java), and we don’t need to write our own. You can write the whole program in Java as:
package academy.learnprogramming.bubblesort;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] numbers = {3, 2, 4, 1, 5};
Arrays.sort(numbers);
for (int num : numbers) {
System.out.print(num + " ");
}
}
}
In C#, we’d use Array.Sort(numbers);
You can experiment with pointers by writing your own implementation of common algorithms. This is an excellent way to practice, which is why I included bubble sort in this post.
Signed byte
Another difference between C# and Java is that the byte type in Java is signed. A Java byte stores a signed value, in the range -128 to +127.
A C# byte is unsigned and stores values from 0 to 255. This difference becomes important when dealing with 8-bit values — Red, Green, Blue (RGB) pixels in an image, for example. The creators of Java justified this, possibly strange, decision to keep things simple. All the other primitive numeric types are signed, and they decided to make byte signed as well. As it turns out, it’s really only a minor inconvenience. It certainly doesn’t prevent you from performing bit manipulation, or reading bytes from an input stream.
To demonstrate the different code that you’ll need in Java compared to C#, we’ll write a small program to manipulate a monochrome bitmap image. I’m not going to discuss the bitmap file format — it’s relatively straightforward and is well-documented online.
We will need an image to experiment with, and I’m going to use a Halloween bat image. I downloaded the png image here. Then I converted it to a monochrome bitmap.
You need the bitmap image that I’ve created (not the original png image from the link). Download this image, by right-clicking it on this page, and make a note of the path where you saved it to.
I’ll start with the C# code, then we’ll look at how to do the same thing in Java.
using System;
using System.IO;
namespace CSBitmapMask
{
class Program
{
static void Main(string[] args)
{
// Read the data into a byte array.
FileStream inFile = new FileStream(@"vintage-halloween-bat.bmp", FileMode.Open);
int len = (int)inFile.Length;
byte[] contents = new byte[len];
inFile.Read(contents, 0, len);
inFile.Close();
// We need to know where the data starts.
// Convert the little-endian 4 bytes to an int
uint pixelDataOffset = BitConverter.ToUInt32(contents, 10);
uint width = BitConverter.ToUInt32(contents, 18);
uint height = BitConverter.ToUInt32(contents, 22);
// bitsPerPixel is stored as 2 bytes, in the bmp file.
ushort bitsPerPixel = BitConverter.ToUInt16(contents, 28);
Console.WriteLine($"Pixel data offset {pixelDataOffset}");
Console.WriteLine($"Image width {width}");
Console.WriteLine($"Image height: {height}");
Console.WriteLine($"Bits per pixel {bitsPerPixel}");
// Read each byte, reversing the bits with XOR.
for (uint index = pixelDataOffset; index < len; index++) {
contents[index] ^= 255;
}
// Write the converted file
FileStream outFile = new FileStream(@"inverted-bat.bmp", FileMode.Create);
outFile.Write(contents, 0, len);
outFile.Close();
}
}
}
I haven’t added any error handling, but you’d use a try/catch block in both languages. For our purposes, it would just clutter up the code. There is a difference between exceptions in C# and Java, and we’ll look at that once we’ve seen the Java code.
The first few lines, lines 10 to 14, read the data from the bitmap file and store it in a byte array. C# reliably reports the size of the data, and we use that size to declare the array (on line 12). That works slightly differently in Java, as we’ll see. Another difference between the two languages is the @ quoted strings on lines 10 and 33. If you include a full path to your bitmap file, you’ll have to double up the backslash character on Windows. To treat the backslash like any other character, you can use the @-quoted strings in C#.
Next, we convert some useful values from the bitmap header information. That’s on lines 17 to 23. The start of the actual image data is at index position 10 in 4-byte values. We also get the image width and height from index 18 and 22 respectively, and the number of bits needed to represent each pixel from index 28. In this case, index 28 has a two-byte value. I’ve printed them out, just to verify the results. Note that we use the C# unsigned types uint and ushort. It wouldn’t make sense for any of those values to be negative.
C# provides a BitConverter class that can convert data from a byte array to several different formats. You can convert to all the C# signed and unsigned integer types, as well as char and bool. It’s a very convenient class because it takes care of the number of bytes for us — we don’t have to specify that we want 4 bytes for an int, for example. We tell it the position in the byte array, and it takes care of the rest. We can see it correctly dealing with a 16-bit value on line 23.
The loop, starting on line 30, uses eXclusive OR (XOR) to invert the bits of each byte. This is a monochrome bitmap image, which means each bit represents a pixel. If the bit is zero, the pixel is drawn in the background color. If it’s a one, the foreground color is used. Inverting the bits switches the colors. Our image shows a black bat on a white background. After using this program to process it, we should get a white bat on a black background.
The final block of code writes the data out to a new file.
Make sure your file paths are correct on lines 11 and 35, then run the program to get an inverted image.
Ok, now let’s see how to do the same thing in Java. The code is only slightly complicated by Java using signed bytes:
package academy.learnprogramming.bitmapmask;
import java.io.*;
import java.nio.file.Files;
public class Main {
public static void main(String[] args) {
try {
// Read the data into a byte array. Backslashes in Windows paths must be typed as \\
File inFile = new File("vintage-halloween-bat.bmp");
byte[] contents = Files.readAllBytes(inFile.toPath());
// We need to know where the data starts - skipping the headers.
int pixelDataOffset = (int)bytesToInt(contents, 10, 4);
long width = bytesToInt(contents, 18, 4);
long height = bytesToInt(contents, 22, 4);
// bitsPerPixel is stored as 2 bytes, in the bmp file.
int bitsPerPixel = (int)bytesToInt(contents, 28, 2);
System.out.printf("Pixel data offset %d%n", pixelDataOffset);
System.out.printf("Image width %d%n", width);
System.out.printf("Image height %d%n", height);
System.out.printf("Bits per pixel: %d%n", bitsPerPixel);
// Read each byte, reversing the bits with XOR.
for (int index = pixelDataOffset; index < contents.length; index++) {
contents[index] ^= 255;
}
// Write the converted file. Backslashes in Windows paths must be typed as \\
OutputStream outFile = new FileOutputStream("inverted-bat.bmp");
outFile.write(contents, 0, contents.length);
outFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Convert the little-endian `size` bytes, starting at `start`, to a long.
*/
public static long bytesToInt(byte[] data, int start, int size) {
long result = 0;
for (int i = 0; i < size; i++){
// result += (int)(data[start + i] & 0xff) << (8 * i);
result += Byte.toUnsignedInt(data[start + i]) << (8 * i);
}
return result;
}
}
The code is very similar. We don’t have an equivalent of @-quoted strings in Java, so you’ll need to use \\ for your backslashes in Windows file paths.
Two’s Complement Most modern programming languages store negative numbers using Two’s Complement. Each bit in the number is inverted, then 1 is added to the result. For the number 2, which is stored as 0000 0010, we invert the bits to get 1111 1101. Adding 1 produces 1111 1110, which is the two’s complement for -2. What’s important here, is that the leftmost bit of a positive number will be 0, and the leftmost bit of a negative number will be 1.
One important difference is in the size of the variables that we must use. In the C# code, width and height are unsigned integers. We can put the 4-byte values into a 32-bit unsigned int without causing any problems. In Java, it’s very common to use a wider variable type than you need to avoid problems with the sign bit. If we were to put 4 bytes into a 32-bit Java int, and the most significant bit of the most significant byte was a 1, then our int would be interpreted as a negative value. Obviously, a negative image width or height wouldn’t make sense (and would cause problems when displaying the image). Using a type that has more bits than we need is a pragmatic approach to avoid problems with the sign bit. Our code uses the long type for the width and height.
The C# BitConverter class provides convenient methods for converting a sequence of bytes to other types, uint and ushort in our code. I haven’t implemented a BitConverter class in our Java code. We only need one method — the bytesToInt method starting on line 43. If you check the source code for the C# BitConverter class, the methods are very similar to the bytesToInt method that I’ve created. However, the C# class accommodates both Big Endian and Little Endian byte sequences.(1) That algorithm is slightly more complex than my bytesToInt method. We could add that functionality if we needed it.
Java 8 added some support for unsigned values, and I’ve used one of those methods on line 47. Java doesn’t have unsigned types, but now supports converting primitive types as though they were unsigned. You convert them to the next wider type. So, the Byte class provides a toUnsignedInt method and the Integer class has a toUnsignedLong method. The equivalent code we’d have used before Java 8 is in the comment on the line above. It uses the bitwise & operator to mask off all but the lower 8 bits. In fact, if you check the source code, that’s all that the toUnsignedInt method of the Java Byte class does.
Masking off the high-order bytes is necessary because the byte 255 (for example) will have all 8 bits set. That represents the value -1 in Java’s unsigned byte type. Assigning that to an int will give a negative value (-1). Masking off the high 3 bytes results in the positive value 255, which is what we want. Complete an internet search for “two’s complement” if you don’t understand how languages like C# and Java store negative numbers.
Most of the differences between the C# and Java code are minor. I’m sure you’d agree that if you can read the code in one language, you’d understand the version in the other. C# doesn’t have a direct equivalent of the readAllBytes method on line 12. But it compensates for that by providing an accurate length of the file stream — on line 12 of the C# code. We can use that to specify the size of the byte array on line 13 of the C# code. The Java code doesn’t need the length of the stream because we can assign the byte array that readAllBytes returns to contents. Each language handles that slightly differently, and both allow us to do the same thing, in their own way.
Java lacks the embedded string interpolation of C#. Instead, we use placeholders and pass the values as additional arguments to printf. The C# equivalent calls to Console.WriteLine are more convenient, but converting from one to the other is easy.
Note that we can use XOR, on line 27 of the Java code, in exactly the same way as we did in C#. Signed data types behave differently when used in arithmetic expressions (adding, dividing, and so forth). But their behavior with bitwise operators isn’t affected by them being signed. We use XOR to invert all the bits, and the fact that Java interprets the byte as a signed value is irrelevant.
Please note that this code is designed to demonstrate the differences between Java and C#. In this case, it highlights the differences between signed and unsigned byte types. This isn’t production-quality code. Although it’s fast, if you were working with bitmaps, then you’d probably want to use Java’s BufferedImage class or the C# Bitmap class. In particular, I’ve taken a bit of liberty with the bitmap specification. This code inverts every bit on the row, including the padding bytes. Each row is padded so that it contains a multiple of 32 bits, and the specification does state that those padding bytes must be zero. In practice, you can display the converted image without problems on Linux, Windows, and Mac. But you shouldn’t deviate from a specification.
Rather than add code to work out how many bits we should leave at the end of each row, we’ll change the code to manipulate the bitmap’s colors instead. That will demonstrate something else to watch out for if you’re working with bytes in Java.
For the C# code, replace the for loop (and its comment) on lines 29 to 32 with this code:
// Background
contents[56] = 255; // Red
contents[55] = 255; // Green
contents[54] = 0; // Blue
Console.WriteLine($"Red: {contents[56]}, Green: {contents[55]}, Blue: {contents[54]}");
// Foreground
contents[60] = 149; // Red
contents[59] = 75; // Green
contents[58] = 75; // Blue
Console.WriteLine($"Red: {contents[60]}, Green: {contents[59]}, Blue: {contents[58]}");
That sets the background color to yellow and the foreground to a bat-like brown color. You can use an online color picker. It can help you get the values for the Red, Green, and Blue bytes.
The corresponding Java code is very similar, but we must be careful when providing byte values to a signed byte type. Replace lines 25 to 28 of the Java code with:
// Background
contents[56] = (byte)255; // Red
contents[55] = (byte)255; // Green
contents[54] = 0; // Blue
System.out.printf("Red: %d, Green: %d, Blue: %d%n", contents[56], contents[55], contents[54]);
// Foreground
contents[60] = (byte)149; // Red
contents[59] = 75; // Green
contents[58] = 75; // Blue
System.out.printf("Red: %d, Green: %d, Blue: %d%n", contents[60], contents[59], contents[58]);
Notice that you must cast any values greater than 127 to a byte. Java enforces the range of values that you can assign to a byte, and you can only use values in the range -128 to 127. You could convert the unsigned values to their signed twos-complement equivalent manually. But casting does that for you.
As this code demonstrates, the absence of unsigned bytes in Java isn’t a big deal. A byte is just a sequence of 8 bits, and we can choose to treat them as 8 bits if that’s what we need.
Java interprets bytes as signed, as you can see in the output of the red, green, and blue values:
Pixel data format 62
Image width 396
Image height 400
Bits per pixel: 1
Red: -1, Green: -1, Blue: 0
Red: -107, Green: 75, Blue: 75
Java displays the value 255 (where all 8 bits are set) as -1. If all bits are set, then Java interprets the signed value as -1.
Checked exceptions
You may be wondering why I included a try/catch block in the Java code. When discussing the C# code, I said that I wasn’t including any error handling, to keep things simple. In our Java code, we don’t have a choice. That’s because Java has two types of exceptions: checked exceptions and unchecked exceptions. If a function throws a checked exception, then you have no choice but to handle it. You either have to use a try/catch block, or use throws to delegate handling of the exception to the calling code.
C# only has unchecked exceptions — you’re free to ignore them if you wish. Of course, ignoring exceptions isn’t a good idea — except in short demonstration code, maybe. Your program will crash if you do get an exception that you don’t handle. But that’s another difference between the two languages. Java has checked exceptions that you must handle in some way. Not all Java exceptions are checked. Both languages also have unchecked exceptions, and you handle them in the same way in both languages.
Java’s checked exceptions are quite controversial, some programmers call them “evil.” Regardless of how you feel about them, they’re a feature of Java, and you must deal with them when converting C# code to Java. In our bitmap code, we didn’t have the option not to handle the exception. We don’t have to use a try/catch block, we could specify that our main method delegates handling the exception to the calling code
public static void main(String[] args) throws IOException {
That lets us remove the try/catch blocks, and the code will now behave the same as the C# code — it will crash if the file isn’t found, or the disk is full.
Generics
You declare generic classes similarly in both languages, but there are a few differences in their behavior.
I’ll use the C# List class and the Java ArrayList class for these examples.
Note that the C# ArrayList isn’t a generic class. If you want a generic list in C# use List. Both ArrayList and List implement the C# IList interface. In Java, the ArrayList class implements Java’s List interface. Here’s a short example that creates a list, initializes it with 3 int values, then passes it to a function that adds the value 100 to each list.
using System;
using System.Collections.Generic;
namespace GenericsExample
{
class Program
{
static void Main(string[] args)
{
List<int> values = new List<int>(){1, 2, 3};
addHundred(values);
foreach (int val in values) {
Console.WriteLine(val);
}
}
public static void addHundred(IList<int> list) {
list.Add(100);
}
}
}
The Java equivalent is very similar, with one important difference — you can’t use primitive types in Java generics. We must use the boxed Integer class as the type, instead of the primitive int type.
package academy.learnprogramming.genericsexample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> values = new ArrayList<>(Arrays.asList(1, 2, 3));
addHundred(values);
for (int val : values) {
System.out.println(val);
}
}
public static void addHundred(List<Integer> list) {
list.add(100);
}
}
Initializing the C# list uses a syntax that will be strange to a Java programmer, but it’s less verbose than the Java equivalent.
There are minor differences — you’ll spot one in the next example. We can write:
ArrayList<HealthSpend> values = new ArrayList<>(); // no need to repeat the type on the right hand side.
in Java, but have to specify the generic type again in the C# code:
List<HealthSpend> values = new List<HealthSpend>();
Because C# allows you to use primitive types (Value types) as the type of a generic type, it doesn’t allow null to be assigned to an object of a generic type. Value types can’t be null inside a generic method. Java’s:
public T t = null;
would become:
public T t = default(T);
in C#. An Integer null isn’t the same as an int Default(int), which would be zero. That’s something to watch out for if you change a Java Integer type to a C# int when converting between the two languages.
Both languages allow you to restrict the range of a generic type. The syntax is slightly different, but you can restrict a generic type to a specified type or subclass. Java also lets you restrict a generic type to a superclass of the specified type, but that’s not possible in C#.
That’s one of two things that may cause problems when converting code from one language to the other. The other thing is that C# lets you restrict the generic type to types that have a parameterless constructor. That allows code like:
T t = new T();
in C# but not in Java. If you see C# code like that in a generic class, you’ll need to find some other way to translate it. That will probably involve reflection or code injection but is certainly possible to translate to Java.
C# struct (and Java Value type)
Another difference you may encounter when moving between C# and Java is the C# struct type.
In C#, a struct is a value type just like int and long. Value and Reference types behave the same in C# and Java, and you should be aware of their difference. Very briefly, one difference is that you can mutate Reference types by a method.
In the two previous list examples, the addHundred method mutates the List and ArrayList Reference types. That’s not possible with a Value type, such as int. Neither language will allow you to mutate the immutable string (C#) and String (Java) Reference types.
The difference is important because the C# struct breaks that rule. A struct is a Value type but can be mutable if it contains a mutable field. Including a mutable field in a struct is a bad idea. Microsoft recommends that you make your structs immutable — since C# 7.2 they provided the readonly keyword to let you do that.
using System;
using System.Collections.Generic;
namespace StructExample
{
public readonly struct HealthSpend {
public readonly string country; // mark fields readonly
public readonly int year;
public readonly double amount;
public HealthSpend(string country, int year, double amount) {
this.country = country;
this.year = year;
this.amount = amount;
}
public override string ToString() {
return $"{country}: {year} ... {amount}";
}
}
class Program
{
static void Main(string[] args)
{
List<HealthSpend> values = new List<HealthSpend>();
values.Add(new HealthSpend("Australia", 2017, 9.2));
values.Add(new HealthSpend("UK", 2017, 9.6));
values.Add(new HealthSpend("USA", 2017, 17.1));
foreach (HealthSpend val in values) {
Console.WriteLine(val);
}
}
}
}
The struct is marked as readonly, and each of the fields is also read-only. That’s achieved by using the readonly keyword, as I’ve done for the country, year, and amount fields.
One advantage of a struct is that it’s far more memory-efficient than a class. But at the moment, you don’t have an equivalent in Java — you’d have to create a small class instead:
package academy.learnprogramming.structexample;
import java.util.ArrayList;
public class Main {
public static class HealthSpend {
private final String country;
private final int year;
private final double amount;
public HealthSpend(String country, int year, double amount) {
this.country = country;
this.year = year;
this.amount = amount;
}
public String getCountry() {
return country;
}
public int getYear() {
return year;
}
public double getAmount() {
return amount;
}
@Override
public String toString() {
return country + ": " + year + " ... " + amount;
}
}
public static void main(String[] args) {
ArrayList<HealthSpend> values = new ArrayList<>();
values.add(new HealthSpend("Australia", 2017, 9.2));
values.add(new HealthSpend("UK", 2017, 9.6));
values.add(new HealthSpend("USA", 2017, 17.1));
for (HealthSpend val : values) {
System.out.println(val);
}
}
}
I’ve made the HealthSpend class a static inner just for convenience — C# allows more than one top-level class in a file, which Java doesn’t allow. There’s no requirement to use a static inner class here. It just saves describing multiple code files in these examples.
Converting the code from one language to the other is easy, but remember that you can use a struct in C# for small classes like this. They’ll be faster (and more memory-efficient) than using a class, which is important when you create large arrays of them.
The Java Valhalla Project is working on introducing user-defined Value Type objects into Java. These will be immutable, the equivalent of a readonly struct in C#. Microsoft recommends that you create immutable struct objects. We expect Java will enforce immutability for its Value objects. But that’s all in the future. At the moment, you’ll need to create a Java class to represent a C# struct. That’s easy, but when converting the other way, be aware that you may be able to use a C# struct and benefit from the better performance it can provide.
If you find a Java class with all fields read-only, then you should consider using a C# struct instead of a class. Look for classes marked as final, or that have getters but no setters.
LINQ vs Stream
One area where C# excels over Java is with its Language Integrated Queries (LINQ). Java 8 added streams to the collections classes, but they don’t have the flexibility of LINQ.
Let’s start with a simple example to demonstrate each approach before we look at the other things that LINQ allows us to do. These examples use our HealthSpend objects from the previous code with a few more items added to the lists. I haven’t included the HealthSpend definitions in these code samples. They’re unchanged from the examples above.
using System;
using System.Collections.Generic;
using System.Linq;
namespace StructExample
{
class Program
{
static void Main(string[] args)
{
List<HealthSpend> values = new List<HealthSpend>();
values.Add(new HealthSpend("UK", 2017, 9.6));
values.Add(new HealthSpend("UK", 2016, 9.7));
values.Add(new HealthSpend("UK", 2015, 9.7));
values.Add(new HealthSpend("Australia", 2017, 9.2));
values.Add(new HealthSpend("Australia", 2016, 9.2));
values.Add(new HealthSpend("Australia", 2015, 9.3));
values.Add(new HealthSpend("USA", 2017, 17.1));
values.Add(new HealthSpend("USA", 2016, 17.2));
values.Add(new HealthSpend("USA", 2017, 16.8));
var amounts = values.Where(hs => hs.country == "Australia")
.OrderBy(hs => hs.year)
.Select(hs => hs);
foreach (HealthSpend val in amounts) {
Console.WriteLine($"{val}");
}
}
}
}
We can do something similar using Java’s Streams:
package academy.learnprogramming.structexample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
ArrayList<HealthSpend> values = new ArrayList<>();
values.add(new HealthSpend("UK", 2017, 9.6));
values.add(new HealthSpend("UK", 2016, 9.7));
values.add(new HealthSpend("UK", 2015, 9.7));
values.add(new HealthSpend("Australia", 2017, 9.2));
values.add(new HealthSpend("Australia", 2016, 9.2));
values.add(new HealthSpend("Australia", 2015, 9.3));
values.add(new HealthSpend("USA", 2017, 17.1));
values.add(new HealthSpend("USA", 2016, 17.2));
values.add(new HealthSpend("USA", 2017, 16.8));
values.stream().filter(hs -> hs.country.equals("Australia"))
.sorted(Comparator.comparingInt(HealthSpend::getYear))
.forEach(System.out::println);
}
}
The necessary capabilities are similar. C#’s OrderBy is easier to use, we just give it a key to sort on. Java’s sorted method needs a Comparator, and the Comparator class provides several methods for creating one from the data. Here, we use an integer comparison, on the year field.
C# produces an enumerable object, and we use a foreach loop to print each item. Java’s Stream class provides a forEach method, which makes the final processing a bit simpler.
Where LINQ wins out, is in its flexibility. Java provides a variety of Stream implementations, in addition to streams from Collections objects. You can get a stream of dates, for example, or some interesting streams from java.net.
C# includes LINQ support for collections of objects, as we’ve just seen, and provides LINQ queries against SQL data sources and XML. Of course, you could create that support for Java streams, but you’d still be chaining method calls together. A LINQ query is a first-class language construct in C# — hence the Language Integrated part of the name.
To demonstrate how useful LINQ queries can be, we’ll perform queries on the source data for Health Spending, worldwide. The data I used in the last few examples came from the World Health Organization (WHO) and is freely available online. For this example, download XML (simple) from https://apps.who.int/gho/data/node.main.GHEDCHEGDPSHA2011?lang=en and make a note of the path you save it to. The data contain entries for each country for each year.
<Data>
<Fact>
<COUNTRY>Afghanistan</COUNTRY>
<GHO>Current health expenditure (CHE) as percentage of gross domestic product (GDP) (%)</GHO>
<YEAR>2017</YEAR>
<Display>11.8</Display>
</Fact>
<Fact>
<COUNTRY>Afghanistan</COUNTRY>
<GHO>Current health expenditure (CHE) as percentage of gross domestic product (GDP) (%)</GHO>
<YEAR>2016</YEAR>
<Display>11.0</Display>
</Fact>
<Fact>
<COUNTRY>Afghanistan</COUNTRY>
<GHO>Current health expenditure (CHE) as percentage of gross domestic product (GDP) (%)</GHO>
<YEAR>2015</YEAR>
<Display>10.1</Display>
</Fact>
<Data>
Within each Fact, we have attributes for the country name, year, and the amount. This is the simple version of the data, so the amount attribute is called Display and only contains one decimal place. That GHO attribute isn’t very useful, because it’s the same for every Fact entry. Before we move on, you need to know that the attribute names are case-sensitive. Fact and Display are in proper case, the other attribute names are uppercase.
I’ll start with the method approach to using a LINQ query — like the previous example.
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace LINQ_Example
{
public readonly struct YearlySpend {
public readonly int year;
public readonly double spend;
public YearlySpend(int year, double amount) {
this.year = year;
spend = amount;
}
}
class Program
{
static void Main(string[] args)
{
string filename = @"C:\Users\Tim\Documents\data.xml"; // Your path here
string filepath = Path.GetFullPath(filename);
XElement whoHealthData = XElement.Load(filepath);
var amounts = whoHealthData.Descendants("Fact")
.Where(fact => (string) fact.Element("COUNTRY") == "Australia")
.OrderBy(fact => (int) fact.Element("YEAR"))
.Select(fact => new YearlySpend((int) fact.Element("YEAR"), (double) fact.Element("Display")));
Console.WriteLine(amounts.Count<YearlySpend>());
foreach (YearlySpend year in amounts) {
Console.WriteLine($"{year.year} : {year.spend}");
}
}
}
}
We filter all the Fact entries by COUNTRY to get the data for Australia. We then sort it by year and select the YEAR and Display attributes.
LINQ also lets you specify queries differently, using the built-in query language. Replace lines 27 to 30 with this query:
var amounts = from fact in whoHealthData.Descendants("Fact")
where (string) fact.Element("COUNTRY") == "Australia"
orderby (int) fact.Element("YEAR")
select new YearlySpend((int) fact.Element("YEAR"), (double) fact.Element("Display"));
That’s a really simple way to query XML data. The query would also work the same if the data were coming from an SQL database. LINQ is a powerful and flexible feature of C#. There’s no Java equivalent, you’d probably use the Java StAX parser to do something similar in Java.
You could also write your own class to parse XML and provide a Stream object from the data. That’s how I’d do it if I had to work with a lot of XML — the result would be very similar to the method call LINQ code (or the earlier Java Stream example). But C# already has that functionality written for you.
It’s a shame it requires an excessive number of casts, but this is one of the easiest ways to parse XML data that I know.
If you need to perform a lot of data querying on different data sources, then definitely consider using C#.
Operator overloading
Another C# feature that Java lacks is operator overloading. There’s much discussion about whether that’s a good thing or not, and I’m not going to discuss the pros and cons of operator overloading here. There’s an ongoing discussion about adding operator overloading to Java. The new Value Types in project Valhalla would strengthen the case for including operator overloading.
At the moment, though, it’s not possible in Java. So, what is operator overloading, and why is it useful? I’ll demonstrate how to use it with a modified version of our C# HealthSpend structure from the previous example.
We’re going to be comparing HealthSpend objects, so we must override the Equals method and also GetHashCode. That’s straightforward, and you’d often do the same thing in Java classes.
using System;
using System.Collections.Generic;
using System.Linq;
namespace OverloadExample
{
public readonly struct HealthSpend {
public readonly string country;
public readonly string year;
public readonly double amount;
public HealthSpend(string country, string year, double amount) {
this.country = country;
this.year = year;
this.amount = amount;
}
public bool Equals(HealthSpend other) {
return (this.country == other.country) & (this.year == other.year) & (this.amount == other.amount);
}
public override bool Equals(object other) {
return other is HealthSpend && Equals(other);
}
public override int GetHashCode() {
return (country, year, amount).GetHashCode();
}
public override string ToString() {
return $"{country}: {year} ... {amount}";
}
public static bool operator ==(HealthSpend hs1, HealthSpend hs2) {
return hs1.Equals(hs2);
}
public static bool operator !=(HealthSpend hs1, HealthSpend hs2) => !hs1.Equals(hs2);
public static HealthSpend operator +(HealthSpend a, HealthSpend b) {
string country = a.country;
if (country == "") {
country = b.country;
}
if ((a.country == b.country) | (a.country == "") | (b.country == "")) {
return new HealthSpend(country, a.year + "-" + b.year, a.amount + b.amount);
} else {
throw new ArithmeticException("Can only add data for the same country");
}
}
}
class Program
{
static void Main(string[] args)
{
List<HealthSpend> values = new List<HealthSpend>();
values.Add(new HealthSpend("UK", "2017", 9.6));
values.Add(new HealthSpend("UK", "2016", 9.7));
values.Add(new HealthSpend("UK", "2015", 9.7));
values.Add(new HealthSpend("Australia", "2017", 9.2));
values.Add(new HealthSpend("Australia", "2016", 9.2));
values.Add(new HealthSpend("Australia", "2015", 9.3));
values.Add(new HealthSpend("USA", "2017", 17.1));
values.Add(new HealthSpend("USA", "2016", 17.2));
values.Add(new HealthSpend("USA", "2017", 16.8));
HealthSpend firstTwoYears = values[0] + values[1];
Console.WriteLine(firstTwoYears);
}
}
}
Line 34 overloads the == operator. The code is very similar to an ordinary method but includes the operator keyword. We can now compare two HealthSpend objects using ==, instead of calling .Equals. I’ve done the same thing with !=, on line 38, but used a lambda that time. You wouldn’t normally mix the two approaches, but I wanted to show you that it’s possible. We implemented both the overloads easily by calling the Equals method on line 18.
On line 40, we override +. In Java, you’d have to write an add method, that would do much the same thing. Of course, you’d have to define how addition should work for your class or struct. In this case, we concatenated the year fields, and the amounts are added.
The output from the addition on line 68 gives an amount of 19.2999 for years 2017 to 2016. We should obviously include some code to concatenate the years with the lowest first. In fact, storing a start and end for the period might be more suitable. But this does demonstrate what overloading the + operator achieves.
We could achieve the same result with an add method in Java (or in C# too, if you prefer that approach). Operator overloading doesn’t let you do anything that you can’t do without it, but it can make complex expressions easier to write (and read).
Other JVM languages, such as Kotlin, do allow operator overloading. Maybe Java will too, but until then it’s fairly straightforward to convert an overloaded operator to an equivalent method call. Line 68 would become:
HealthSpend firstTwoYears = values.get(0).add(values.get(1)); |
in Java.
Extension functions
C# has another useful feature that’s not available in Java — extension methods. These are functions that you can call as though they’re part of a class (or struct). They allow you to provide more functionality without creating a subclass. An extension method is a static method that’s called on a class instance. Extension methods implement much of LINQ’s functionality. I’ll use our earlier LINQ query to demonstrate how they can be useful. Change the main method in the previous example to the following:
static void Main(string[] args)
{
List<HealthSpend> values = new List<HealthSpend>();
values.Add(new HealthSpend("UK", "2017", 9.6));
values.Add(new HealthSpend("UK", "2016", 9.7));
values.Add(new HealthSpend("UK", "2015", 9.7));
values.Add(new HealthSpend("Australia", "2017", 9.2));
values.Add(new HealthSpend("Australia", "2016", 9.2));
values.Add(new HealthSpend("Australia", "2015", 9.3));
values.Add(new HealthSpend("USA", "2017", 17.1));
values.Add(new HealthSpend("USA", "2016", 17.2));
values.Add(new HealthSpend("USA", "2017", 16.8));
var amounts = values.Where(hs => hs.country == "Australia")
.OrderBy(hs => hs.year)
.Select(hs => hs)
.Sum();
Console.WriteLine(amounts);
}
The LINQ query is like the previous one, but instead of returning a list of HealthSpend items, we want the sum of the amounts. Unfortunately, we get an error — there’s no Sum method in our HealthSpend structure.
Of course, we could add one. But if we didn’t have the source code for HealthSpend, or didn’t want to modify it, then we’d have to resort to creating a subclass. But you can’t subclass a struct! This is where an extension method can be useful.
} // End of HealthSpend definition
public static class HSExtensions {
public static HealthSpend Sum(this IEnumerable<HealthSpend> source) {
HealthSpend? sum = null; //Nullable<HealthSpend>;
foreach (HealthSpend val in source) {
if (sum == null) {
sum = new HealthSpend(val.country, val.year, val.amount);
} else {
sum += val;
}
}
if (sum == null) {
return new HealthSpend();
} else {
return (HealthSpend)sum;
}
}
}
class Program
{
...
We create a class to contain the extension method(s). I’ve put it in the same file, but you could put it in its own file and use HealthSpend; at the start of the file.
Because a struct can’t be null (it’s a Value type, remember) we use ? after the type, on line 55. That allows a Value type, T, to be null by boxing it into an instance of Nullable<T>. We cast sum back to HealthSpend before returning it.
Our code will now run and uses this Sum extension method at the end of the LINQ query. That’s pretty cool, and would be harder to implement in Java — you’d probably subclass the HealthSpend class if you couldn’t modify the source code. Or you could remove Sum from the query and perform the summation separately. But doing that wouldn’t have demonstrated an extension method.
Partial classes and methods
There’s not much to say about these. C# allows you to split a class, struct, or interface over several files. As a result, you may need to include the declaration of a method in one file and its definition in another. Converting a partial class to Java just involves combining the two (or more) files into a single file. Going the other way, from Java to C#, doesn’t pose a problem — you can always split the class up into separate files later if you feel the need.
Microsoft has many reasons for including partial classes. It makes it easier for several developers to work on the same class and automatically generate code in its own file. The first reason should probably make you take another look at the class. If it’s really that big, then maybe you’ve got a god class and should consider refactoring it. The ability to keep automatically generated code separate can be useful. Microsoft’s documentation mentions Windows Forms. But you’re unlikely to be using Windows Forms in your Java app. So, the lack of partial classes won’t be a serious consideration when converting the code.
Summary
Although there are a lot of similarities between C# and Java, there are also quite a few important differences to be aware of. I haven’t covered every single difference in this article. I have shown most of the things that you should be aware of or that might cause problems when moving from one language to the other.
Microsoft has a PDF showing some of the differences between C# and Java. But be aware that this was produced back in 2010, and both languages have evolved since then. Java 10 now supports type inference for local variables. For example, it uses exactly the same var keyword as C#. Both languages continue to evolve, although Java tends to evolve slower than C#. Java focuses more on stability, over adding new features.
You’ll have noticed that I made no attempt to answer the question “Which is better?” Unless you define exactly what you mean by “better,” the question is meaningless. The LINQ XML example demonstrates that C# might be a better choice if you want to perform a lot of queries against SQL or XML data. You won’t have to get into the nuts and bolts of either Sequel or XML. LINQ doesn’t allow you to do anything that you can’t do in Java, but it can make it a lot easier. Paradoxically, that doesn’t necessarily mean that you should choose C# over Java for that kind of work. If you are doing a lot of that type of query, then you can invest a bit of time to extend the Java Stream interface. It will let you produce code that’s as easy to write as LINQ queries. Admittedly you’d have to chain method calls, rather than using the LINQ query syntax, but doing that uses fewer casts than you must use with LINQ. If I must query XML, I’m more likely to write in C# instead of Java because it’s not something I do very often, not because it’s something I do a lot.
I’ve tried to produce an objective description of the major differences between the two languages. Hopefully, this article will help you decide which language you should start with. We learned that it doesn’t really matter which you choose initially. Both are great languages to start your programming career, and once you’ve mastered one, you’ll have no problem moving to the other. This article has also shown how easy it can be to switch from one language to the other. But, there are a few things you need to look out for when switching from one language to the other.
If you’d like to learn more about either (or both) languages, check out my Udemy courses. My Learn C# for Beginners Crash Course is a great option in case you’re just getting started. Wondering about the difference between Java and Python? I have another great article that compares these two popular programming languages. Check it out!
Notes: 1. If you’re not sure what Big and Little Endian means, google those terms. Put simply, we write numbers Big Endian, so the highest digit appears first (on the left when reading from left to right). The number 1234 written using Little Endian would become 4321. The units come first, then the tens, then the hundreds, then the thousands. Intel processors store values using Little Endian. Many mini-computers and mainframes use Big Endian format.
Recommended Articles
Top courses in C# (programming language)
C# (programming language) students also learn
Empower your team. Lead the industry.
Get a subscription to a library of online courses and digital learning tools for your organization with Udemy Business.