May 10, 2020

C# vs. Java: 5 Irreplaceable C# Features We’d Kill to Have in Java

Table of Contents

Key takeaway

If we could have the best of both worlds between C# and Java, what would that look like?

The perfect programming language doesn’t exist. I hope we can agree on that, if nothing else. New languages are often developed in response to the shortcomings of another, and each is inevitably stronger in some ways and weaker in others.

C# and Java both stemmed from C/C++ languages, and they have a lot in common beyond both being object-oriented. In addition to some structural similarities between Java’s JVM and C#’s .NET CLR, each advanced on its own path with their respective development teams focused on different visions of what the language should be.

We don’t want to get lost in the argument of which language is better than the other, we just want to outline some of the features that developers in C# are using that we don’t have available to us in Java.

Let’s get started.


LINQ (Language-Integrated Query) was introduced to C# in 2007 to help developers query data from various sources. With it, we can write queries without needing to take into consideration the appropriate syntax for the specific database being called on. A component of LINQ, the LINQ provider, converts the query to a format that’s readable by the underlying source. For example, if we need to query data from a SQL database, the LINQ to SQL provider will convert the LINQ query into T-SQL so that the database can understand it.

To perform a query operation in LINQ, first the database is obtained, then the query is created and finally it’s executed. In LINQ to Object queries, this can be as simple as a single line of code, rather than writing complicated iterations of nested for each loops.

For example, let’s look at this code for filtering 2-digit numbers from a list in C#.
First, without using LINQ:

List<int> FilterTwoDigitNumbersWithoutLinq(List<int> numbers)
       var tens = new List<int>();        

        for (var i=0; i < numbers.Count(); i++)    
               if ((9 < numbers[i]) && (numbers[i] < 100))        
         return tens;

And then using LINQ in query syntax:

List<int> FilterTwoDigitNumbersWithLinq(List<int> numbers)
            return (from a in numbers            
                        where (a > 9 && a < 100)            
                        select a).ToList();

And method syntax:

List<int> FilterNonTwoDigitNumbersWithLinq2(List<int> numbers)
            return numbers.Where(a => a > 9 && a < 100).ToList();

Both syntaxes here are correct, the only real difference is that the query syntax looks more like SQL and the method syntax uses lambda expressions (and thus, looks like something we might write in Java). Bottom Line: Many of the features that LINQ relies on, such as lambdas, are useful in their own right and already have equivalents that were implemented in Java. So, while we can use streams and lambdas for querying data, LINQ streamlines the process and removes much of the verbosity that still exists in Java.

2. Struct

Structs in C# are used similarly to classes. In fact, a struct can even be considered to be a “lightweight class” itself as it can contain constructors, constants, methods and more. The biggest difference between a struct and a class is that structs are value-types while classes are reference-types.
The most significant benefit of writing a struct over creating a class is that it is easier to ensure value semantics when constructing a value-type than it is when constructing a reference-type. As stated in Microsoft’s documentation, “a variable of a struct type directly contains the data of the struct, whereas a variable of a class type contains a reference to the data.” So, one of the benefits of using a struct over a class is that the only way to alter its value from other parts of the code is by explicitly passing it as a reference.

Developers at Microsoft recommend using a struct in place of a class only for types which are smaller than 16 bytes, are immutable, are short-lived and are not frequently boxed. Under these circumstances, using a struct may also be slightly more efficient than using a class because it will more likely be stored in the stack rather than in the heap.


public struct Point    
          public int X;        
          public int Y;        

          public Point(int X, int Y)        
                    this.X = X;            
                    this.Y = Y;        

          public static Point operator +(Point p1, Point p2)        
                    return new Point(p1.X + p2.X, p1.Y + p2.Y);        
          public override string ToString()        
                     return ($"({X}, {Y})");        
class Program    
           static void Main(string[] args)        
                    Point point1 = new Point(1, 5);            
                    Point point2 = new Point(2, 3);            

                   Console.WriteLine("The addition of both points will result in: {0}", (point1 + point2));            


Bottom Line: In many situations, writing a struct can appear to save time on memory allocation and deallocation and, thus, be more appealing. The truth is, though, that value-types are stored wherever they are owned. Regardless of the apparent benefits or drawbacks of using structs, we don’t have to worry about any of this when it comes to Java.

3. Async/Await

By calling async on a code part, or more specifically on a method, that method will be executed on a separate thread so as to not block the current thread. When the code reaches the await command, it will continue running. If at that point, the async code hasn’t finished, then the execution will return to its calling method.

This can help improve the overall responsiveness of your application and help to reduce performance bottlenecks. Using asynchronous programming is very important for applications when trying to access the web and for all UI-related activities. Compared to previous techniques of implementing asynchronous programming, the use of async/await preserves the logical structure of your code and the compiler does the heavy lifting that used to be required of the developer.


class Program    
                public static void Main()        
                {            Console.WriteLine("Hey David, How much is 98745 divided by 7?");            

                              Task<int> david = ThinkAboutIt();            

                              Console.WriteLine("While he thinks, lets chat about the weather for a bit.");            
                              Console.WriteLine("Do you think it's going to rain tomorrow?");            
                              Console.WriteLine("No, I think it should be sunny.");            

                              var davidsAnswer = david.Result;            

                              Console.WriteLine($"David: {davidsAnswer}");            


               private static async Task<int> ThinkAboutIt()        
                              await ReadTheManual();            

                              Console.WriteLine("Think I got it.");            

                              return (98745 / 7);        

               private static async Task ReadTheManual()        
                              string file = @"D:\HowToCalc.txt";            

                              Console.WriteLine("Reading a manual.");                        

                               using (StreamReader reader = new StreamReader(file))            
                                        string text = await reader.ReadToEndAsync();            

The output:

// Possible Output:

Hey David, How much is 98745 divided by 7?
Reading a manual.
While he thinks, lets chat about the weather for a bit.
Do you think it's going to rain tomorrow?
No, I think it should be sunny.
Think I got it.
David: 14106

Bottom line: CompletableFutures undoubtedly brought us closer to having equivalent capabilities in asynchronous programming in C# and Java. Still, the complicated nature of using it makes it no match for the ease with which the async/await keywords can be implemented.

4. Lazy<T> Class

Whether working in C# or in Java, many of us have implemented lazy initialization (or instantiation) so that an object is not created until the first time that it will be used. One of the more common instances that lazy initialization is used for is when an application loads many objects upon launching but only requires a few of them initially. In this case, we want to instruct unnecessary objects to initialize only when needed to improve the performance of our application.
Bottom Line: Implementing lazy initialization recently became much easier in Java (as did many other things) when lambda expressions were introduced in Java 8. Still, in C# we can use the Lazy<T> wrapper class which provides the semantics of lazy initialization for any class library or user-specified type.

5. Some Keyword Equivalencies

Useful features in languages don’t have to be as big of an undertaking as implementing something like LINQ in C# or modules in Java. Here are some keywords that help C# developers that we don’t have in Java:

a. as

The as keyword in C# attempts to safe-cast an object to a type, and if it can’t it returns null. Java’s instanceof is almost comparable, but it is a boolean that returns true if the types match and false if they don’t.

b. Yield

Yield and return yield are used in C# to perform custom and stateful iterations without an explicit extra class and without the need to create a temporary collection. Our best options for implementing iterations in Java seem to be accessing an external library or using lambdas which were introduced in Java 8.

c. var

Var is an implicit type that is determined by the compiler and is functionally equivalent to writing an explicit type (i.e. int, string, etc.). Aside from saving a few extra keystrokes, var allows for anonymous types which are most typically used in LINQ queries. We’re expecting to see a “var” identifier implemented in the newly anticipated Java 10 which will “extend type inference to declarations of local variables with initializers.”

d. Checked

In C#, we use the checked keyword to explicitly enable overflow checking for integral-type expressions. If the resulting value of some expression is outside the range of the destination type, we can use checked to force the runtime to throw an OverflowException. This is helpful because while constant expressions have overflow checking at compile time by default, non-constant expressions do not.

Tooling Ecosystems

There are many more differences between Java and C#, of course, some of which are rooted in the differences in the Java and .NET frameworks. With these differences, come differences in compatibilities of helpful tools that provide production monitoring and error tracking.

One of those tools is,  shows developers the complete source code and variable state across the entire call stack for every error in production. It currently offers support for JVM based languages, and .NET framework compatibility is coming in the next few months.

Final Thoughts

At the end of the day, most of the features we mentioned here give C# developers an advantage in terms of the length and clarity of their code rather than in enabling them to write code that can’t be written in Java. It’s also true that much of what made Java a more verbose language was addressed, at least partially, by the inclusion of lambda expressions in the most recent version update. Still, a lot of these features that we find in C# and not in Java simplify the syntax of common use cases beyond what lambdas offer.

Again, we don’t want to get involved in the never-ending argument over which language is better, we’re just here to point out some of the differences between the two. Did we miss any features that you would love to have in Java? Let us know in the comments!

You might also like
No items found.

Similar Blogs

No items found.
Service Reliability Management