DEV Community

Darya Shirokova
Darya Shirokova

Posted on

Learning C# After Java: 5 Differences Explored

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:

static void PrintHello(string name, string surname) =>
            Console.WriteLine($"Hello, {name} {surname}!");
Enter fullscreen mode Exit fullscreen mode

The following invocations are identical in terms of result:

PrintHello("John", "Doe");
PrintHello(name: "John", surname: "Doe");
PrintHello(surname: "Doe", name: "John");
Enter fullscreen mode Exit fullscreen mode

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

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

static void Main(string[] args)
{
    PrintHello();
    PrintHello("Peggy");
}
Enter fullscreen mode Exit fullscreen mode

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:

class Student
{
    public string Name {get; init;}

    public string? Department {get; set;}
}
Enter fullscreen mode Exit fullscreen mode

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:

var student = new Student { Name = "Alice", Department = "Computer Science"};
student.Department = "Chemistry";
Console.WriteLine(student.Department);
Console.WriteLine(student.Name);
Enter fullscreen mode Exit fullscreen mode

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:

string department = null;
if (student != null) {
    department = student.Department;
}
Enter fullscreen mode Exit fullscreen mode

can be replaced with:

string department = student?.Department;
Enter fullscreen mode Exit fullscreen mode

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:

Console.WriteLine(student?.Department ?? "Value not known");
Enter fullscreen mode Exit fullscreen mode

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 interface in C# is an analogue of Iterable 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.

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

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

foreach (var num in evenNums)
{
    Console.WriteLine(num);
}
Enter fullscreen mode Exit fullscreen mode

In Java, the same code would look like this:

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

evenNums.forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

In addition to the invocation of the query methods as shown in the example above, C# provides Language-Integrated Query (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:

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);
}
Enter fullscreen mode Exit fullscreen mode

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:

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);
}
Enter fullscreen mode Exit fullscreen mode

You can check this blogpost 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:

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.");
    }
}
Enter fullscreen mode Exit fullscreen mode

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#!

Top comments (0)