Categories
Uncategorized

C# vs Java: Why Microsoft is (slowly) winning developers

C# (pronounced C#-sharp) is not an old software programming language, nor is it bleeding-edge as far as features go. But it serves as one of the best for writing upper-level software because of its ongoing development, cross-platform support, simplification of frequent tasks. But this monumental task took years and several iterations to become what some would consider the future of cross-platform software development

When you ask a developer “What is C#?”, one of their first responses may be “The Microsoft-version of Java”. For years, that wasn’t much of an incorrect explanation. The language is almost entirely inspired by Java’s syntax and methods. It was also almost entirely exclusive to the Microsoft Windows platform for well over a decade. But as time goes on, we see C# go from copycat to a serious competitor to Java, to what may be its inevitable replacement. Any advantage Java had in terms of support is being wiped away as the language, and its default libraries (known as “.NET”) have become democratized as an open-source foundation.

History

To see how the languages diverged from at one point being very similar, it’s important to have a grasp of how it played out

Prehistory

In the 1980s and early 1990s, there were two emerging types of programs: Compiled binaries, and Interpreted scripts, both of which are used to this day. Compiled programs utilize encoded instructions (the “code”) and use a special program (the “compiler”) that translates it into machine code (a series of compact instructions that the processor can read). This was how programs were compiled for a long time, as the process was quicker, more specialized to the hardware, and could be accomplished with fewer resources, which were precious at the time. Interpreted script on the other hand utilized human-readable code, but instead of compiling it, the machine ran a separate program (called the “interpreter”) to translate the instructions and tell the processor what to do on-the-fly. This was slower and took more resources, but code could be ported to other machines much more easily. Because processors and operating systems can differ by so much, other programs must be re-compiled for different machines, whereas interpreters can run the code anywhere, at the cost of some performance and resources.

Java’s Headstart

Enter Java in 1995. Under-the-hood, Java is actually a hybrid approach meant to appeal to the best of both worlds. In order to run a Java program, you need to write your code, compile it, then run it. However, unlike other compiled languages, you need to use the Java Virtual Machine (JVM) to interpret your Java program. So instead of re-compiling the program for every type of machine, you would simply install the JVM (when people say “I need to install Java”, this is what they mean) then their computer can run any Java program. This whole process actually leads programmers to be quicker than interpreted languages, but you only have to compile the program once!

At the time, that was quite an experimental concept, but that isn’t the only experimental concept it employed. Java became one of the first mainstream programming languages to utilize a purely object-oriented structure. Beforehand, many programmers needed to write code in functions. Every program had to start somewhere, then it needed to call other functions to branch off. That was about as abstract as it got. But with Object-Oriented Programming (OOP), programmers could instead model data as “objects” and make them interact with each other. The interpreter would also manage memory for them using a special process called the “garbage collector” (GC). The GC would detect when an object was no longer used, and it would destroy it in order to free resources for other objects later. This was very different from other languages that required you to manually tell the computer to free memory when you no were no longer using it.

Java was initially designed for use in dynamic hardware such as television remotes, since each remote and each television were different, but still needed to communicate. However, the technology was too advanced and too resource-intensive to work, and in 1995, many computers were doing just fine with compilers and interpreters alone. But Java found its appeal on one place: The web. Netscape Navigator. The famous web browser needed a way for users to interact with web pages that goes beyond texts, links, and images. Java provided that framework on a wide scale to machines that did not normally need to be loaded so fast.

These factors lead Java to be far ahead of its time, and adoption on the web was swift, but as time went on, competition was bound to come, as these features proved essential for the future of software development.

Meet C#

C# was released in 2002, and was included with Visual Studio .NET 2002, with the goal being a general-purpose alternative to Java for Microsoft Windows. Many of the same features Java includes were a standard. Applications were faster and compiled specifically for Windows, giving them an advantage. Much of what C# developers take for granted now was not present, but as time went on, the support was maintained. C# and .NET are both technically two different products, but they are practically unable to function without one another. C# would have nothing to be able to run on the system without .NET, and .NET would have nothing to run it without C#.

Microsoft had gone through some darker times in the early-to-mid 2000s. With an antitrust lawsuit from Netscape, and the eventual loss of the smartphone market, this ultimately lead the company to branch away from the locked-down software platform (comparable to how Apple has a locked-down hardware platform). In the years to come, C# would become more than “Java for Windows”, and in the modern-day, poses a serious threat to Java’s dominance.

It’s All in the Libraries

Java is maintained by Oracle, who makes money through hosting, data, and licensing. This isn’t much different from Microsoft, whose primary source of income is licensing software such as Windows, as well as hosting applications on the web platform. But a core difference was that Microsoft decided to open up their famed .NET Framework to other platforms years later, while Java had been open to other platforms the entire time. The .NET Framework was eventually replaced by .NET Core in 2016. Unlike its predecessor, these default libraries were open-source, and the community would be able to have input on every detail of what developers would be able to do by default. Most importantly, this transition enabled C# programs to be run on Linux, MacOS, iOS, Android, and others.

Since C# was released later and by a larger software company, lessons were taken from Java that were embedded into the language from the beginning. With C# now being truly cross-platform like Java, Java now has a true alternative that operates very similarly.

As of the writing, .NET Core was rebranded to just “.NET”. This signifies that there will only be one platform going forward. .NET version 5.0 is coming out, as well as C# 9.0. In the coming years, it is expected Microsoft will develop the Multiple App User Interface (MAUI) framework, which will allow one C# app to run on any device without extra code, as beforehand, even .NET Core apps required specific UI to run for each platform.

Compare and Contrast

As mentioned, C# and Java are both object-based and have very similar syntax, but for compatibility and simplicity, Java is notoriously less-optimizable. To start, here is a “Hello World” program written in Java and C#:

//HelloWorld.cs
using System;
namespace HelloWorld
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}
//HelloWorld.java
package HelloWorld;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

As first glance, the syntax is almost identical. Some key things to note though:

  • Many of C#’s keywords and concepts, such as “namepace” instead of “package” are also inspired by C and C++. This is important because other concepts meant to optimize the code are also inspired from C and C++, such as structures (called “structs”, which are handled differently than objects, and do not exist in Java).
  • “string” is treated like a primitive type in C# (hence why it is highlighted and all lowercase). At a lower-level, Strings of characters and arrays are not like other variables in that they cannot be hard-coded length, and can change how large they are. This is important for managing how much memory the computer has. C# automatically handles this. While Java is also good with handling strings compared to C and C++, it doesn’t have the privilege of being treated like a default type. This is a core difference in how you write code in either language.
  • The conventions for how you write code are a little different. Java uses “camel case” and abbreviations more frequently (for example, “println” instead of “PrintLine”). Many C# developers will also place the curly brackets “{ }” on new lines instead of on the same line, unlike in Java.

A commonly-mentioned difference between C# and Java is that in Java, every non-primitive type is treated like a reference type, and that objects must be “passed by reference”. This means that even when the object is small and guaranteed to be the same size, the values are not cloned and passed in, but instead are looked by by other functions using an address. This may seem like a good idea, but constantly referencing an address takes time from the processor, and there are many instances where the value doesn’t need to be referenced, but instead just given to the function.

This is where structures (“structs”) come in. Before classes, structs were the only way to organize data. C# adds to this standard to re-enable “value types”. This is one thing Java programs do not have, and it could be argued that ideally no object-oriented system would need them. But for the sake of performance, it’s critical to have value types available. Below is an example program written in C# using structs, vs Java using classes:

//AlertKeeper.cs
using System;
using System.Collections;
namespace AlertKeeper
{
    public enum AlertType
    {
        Emergency,
        Error,
        Warning
    }
    public struct Alert
    {
        public DateTime TimeCreated { get; set; }
        public AlertType TypeOfAlert { get; set; }
        
        public override string ToString()
        {
            string alert = "";
            switch(TypeOfAlert)
            {
                default:
                    break;
                case TypeOfAlert.Emergency:
                    alert = "Emergency";
                case TypeOfAlert.Error:
                    alert = "Error";
                case TypeOfAlert.Warning:
                    alert = "Warning";
            }
            return $"Alert date: {TimeCreated.ToString()} - {alert}!";
        }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            List<Alert> alerts = new List<Alert>();
            alerts.Add(new Alert() { TimeCreated = DateTime.Now, AlertType = TypeOfAlert.Error });
            alerts.Add(new Alert() { TimeCreated = DateTime.Now.Add(TimeSpan.FromSeconds(5)), AlertType = TypeOfAlert.Emergency });
            
            Console.WriteLine("Alerts and their times:");
            foreach(Alert a in alerts)
            {
                Console.WriteLine(a.ToString());
            }
        }
    }
}
//AlertKeeper.java
package AlertKeeper;

import java.util.ArrayList;
import java.time.LocalDateTime

public enum AlertType {
    Emergency,
    Error,
    Warning
}
public class Alert {
    private LocalDateTime timeCreated;
    private AlertType typeOfAlert
    
    public LocalDateTime getTimeCreated() {
        return timeCreated;
    }
    public void setTimeCreated(LocalDateTime timeCreated) {
        this.timeCreated = timeCreated;
    }
    public AlertType getTypeOfAlert() {
        return typeOfAlert;
    }
    public void setTypeOfAlert(AlertType typeOfAlert) {
        this.typeOfAlert = typeOfAlert;
    }

    @Override
    public String toString()
    {
        String alert = "";
        switch(typeOfAlert)
        {
            default:
                break;
            case TypeOfAlert.Emergency:
                alert = "Emergency";
            case TypeOfAlert.Error:
                alert = "Error";
            case TypeOfAlert.Warning:
                alert = "Warning";
        }
        return String.format("Alert date: %s - %s!", timeCreated.toString(), alert);
    }
}
public class Program {
    public static void main(String[] args) {
        ArrayList<Alert> alerts = new ArrayList<Alert>();
        
        Alert newAlert = new Alert();
        newAlert.setTimeCreated(LocalDateTime.now());
        newAlert.setAlertType(TypeOfAlert.Error);
        alerts.add(newAlert);
        
        newAlert = new Alert();
        newAlert.setTimeCreated(LocalDateTime.now().plusSeconds(5));
        newAlert.setAlertType(TypeOfAlert.Emergency);
        alerts.add(newAlert);
        
        System.out.println("Alerts and their times:");
        for(Alert a : alerts) {
            System.out.println(a.toString());
        }
    }
}

These programs will perform the same abstract operations and print the same results in the console, but you’ll notice some of these differences in syntax in play. But most importantly, it’s worth pointing out the features C# offers. “Alert” is a class in Java, and a struct in C#. This seems subtle, but the system will handle it quite differently, and the programmer must be ready for that when writing code. In addition, the C# language has many funky-looking operations going on that ultimately simplify the process of reading writing it. For example, when the Alerts are created in C#, Properties are used, which handle the ordinary “get” and “set” functions you see in the Java program. Those are used in the curly brackets when creating the Alerts so that the programmer can easily read it, and modify it if needed.

Conclusion

In the prior example, both languages ultimately do the same thing and in a very similar way, but it’s simple features and syntax that go a long way in differentiating modern C# from modern Java. A lot of development is put into both languages and platforms. But Java ultimately has a long way to go before being able to compete with C# in terms of efficiency and features. Java is far more popular at the time of writing, but its arguably due to it being in existence longer and having been cross-platform since the beginning. Now that the latter has changed, and support for C# is booming, that may not be the case for much longer.

Further Reading