Learning C# After Java: 5 Differences Explored
Tags: csharp, java, beginners
Published: 2023-06-13 09:18:37

There are many similarities between Java and C# - they are both managed languages with somewhat similar syntax influenced by C, but there are many differences as well. In this blogpost we will explore some of the key distinctions that Java developers should be aware of when transitioning to C#.

### 1 - Syntax sugar

Let's explore three language features that C# offers to enhance code readability, which are not present in Java. This is by no means an exhaustive list.

**Named and Optional Arguments**
In C#, you can use the name of the parameter to specify arguments of the method in arbitrary order. For example, consider the following method:

```csharp
static void PrintHello(string name, string surname) =>
            Console.WriteLine($"Hello, {name} {surname}!");
```

The following invocations are identical in terms of result:

```csharp
PrintHello("John", "Doe");
PrintHello(name: "John", surname: "Doe");
PrintHello(surname: "Doe", name: "John");
```

Additionally, you can specify a default value for the parameter, in which case it becomes optional:

```csharp
static void PrintHello(string value="World") =>
            Console.WriteLine($"Hello, {value}!");

static void Main(string[] args)
{
    PrintHello();
    PrintHello("Peggy");
}
```

**Get/Set Properties**

A property in C# is a member that offers a versatile approach to access, modify, or compute the value of a private field. It is an analogue of regular getters/setters in Java with a special syntax and an option to auto-implement them.

Consider the following example with auto-implemented properties:

```csharp
class Student
{
    public string Name {get; init;}
    
    public string? Department {get; set;}
}
```

Here we declare auto-implemented properties. The `Name` property exposes get property, as well as setter that can only be used during initialization of the method (`init`). The `Department` property exposes both a getter and a setter. We can then access and modify the properties accordingly:

```csharp
var student = new Student { Name = "Alice", Department = "Computer Science"};
student.Department = "Chemistry";
Console.WriteLine(student.Department);
Console.WriteLine(student.Name);
```

**Null-Conditional (Elvis) and the Null-coalescing operator**

Unlike Java, C# supports Elvis (`?.`) and the Null-coalescing operators (`??`). These operators aim to simplify work with nullable values. For example, this block of code:

```csharp
string department = null;
if (student != null) {
    department = student.Department;
}
```

can be replaced with:

```csharp
string department = student?.Department;
```

The null-coalescing operator allows to replace `val != null ? val : defaultValue` expression with `val ?? defaultValue`. It can also be used in combinations with the Elvis operator, for example:

```csharp
Console.WriteLine(student?.Department ?? "Value not known");
```

### 2 - Exceptions

In Java, there are so called checked and unchecked exceptions. Checked exceptions are checked at compile time and if you use a method that throws a checked exception, you must process it in your code, otherwise it won’t compile. Unchecked exceptions on the other hand don’t have to be processed in the code. If they’re not caught, they will crash the thread they’re thrown on.

C# doesn’t have the concept of checked exceptions and this is always up to developers to decide how and when to process an exception.

### 3 - IEnumerable & LINQ

[IEnumerable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.ienumerable?view=net-7.0) interface in C# is an analogue of [Iterable](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) interface in Java. To support lazy computations over Iterable objects, Java 8 introduces a separate Stream API. In C#, similar functionality is provided directly via the [Enumerable class](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-7.0).

Here is an example of using Where method of Enumberable to filter even numbers in C#:

```csharp
IEnumerable<int> evenNums = Enumerable.Range(1, 1000)
				.Take(5)
				.Where(num => num % 2 == 0);

foreach (var num in evenNums)
{
    Console.WriteLine(num);
}
```

In Java, the same code would look like this:

```csharp
List<Integer> evenNums = Stream.iterate(1, i -> i + 1)
        .limit(5)
        .filter(num -> num % 2 == 0)
        .toList();

evenNums.forEach(System.out::println);
```

In addition to the invocation of the query methods as shown in the example above, C# provides Language-Integrated Query ([LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/)) capabilities. This is a technology that allows to use query language on IEnumberable objects directly in C#.

The example to filter even number will look like this in a LINQ-style syntax:

```csharp
var nums = new List<int> { 1, 2, 3, 4, 5 };

IEnumerable<int> evenNums = from num in nums
                                where num % 2 == 0
                                select num;

foreach (var num in evenNums)
{
    Console.WriteLine(num);
}
```

### 4 - Async / await

C# provides async/await keywords to write asynchronous code in a more readable and sequential manner. Consider the following example, where we declare an asynchronous method that generates a random integer after 2 seconds of delay. We first call the method twice to generate two random integers and then await for the result when we need to print the sum:

```csharp
static async Task<int> RandomIntAsync()
{
    await Task.Delay(TimeSpan.FromSeconds(2));
    return new Random().Next(0, 100);
}

static async Task Main(string[] args)
{
    Task<int> val1 = RandomIntAsync();
    Task<int> val2 = RandomIntAsync();
    Console.WriteLine(await val1 + await val2);
}
```

You can check [this blogpost](https://dev.to/daryashirokova/understanding-asynchronous-code-a-comparison-of-asynchronous-programming-in-c-and-java-fh9) where I explore the differences in how the asynchronous code is executed in C# and Java in more details.

### 5 - Nullable

C# treats all value type variables as non-nullable by default. To allow a null value, the type must be explicitly marked as nullable, one should simply add a question mark to the type definition (e.g. "int?"). This allows developers to process situations where data may be missing or undefined, for example:

```csharp
class Person
{
    public string Name { get; init; }

    public int? Age { get; init; }
}

static async Task Main(string[] args)
{
    var person = new Person { Name = "Bob"};
    if (person.Age == null)
    {
        Console.WriteLine("Age not specified.");
    }
}
```

In addition to nullable value types, you can specify nullable reference type, just like we did above in a Student class example where we declared Department string as nullable. Historically in C#, all references are nullable by default. However starting with C# 8, references are assumed to be non-nullable by default unless explicitly stated, similar to how value types behave. Due to complications with backward compatibility, this feature is not enabled by default.

### Conclusion

Here we had a brief overview of some (but not all) of the major differences between C# and Java. Hope this provides a starting point when switching from Java to C#!