Java’s type erasure means you can’t do any of these things with generics:
// Java
public class MyClass<E> {
public static void myMethod(Object item) {
if (item instanceof E) { //Compiler error
...
}
E item2 = new E(); //Compiler error
E[] iArray = new E[10]; //Compiler error
}
}
you instead have to pass a “Class” object around if you want to check instances or create new instances of those generics on the fly which results in ugly code like this where you have to pass a “Class” object at the callsite.
// Java
public static <E> boolean containsInstanceOf(List<E> Arraylist, Class<? extends E> clazz) {
for (E e : Arraylist) {
if (clazz.isInstance(e)) {
return true;
}
}
return false;
}
so that makes it annoying for certain use cases. but it also makes it nearly impossible to handle generics INSIDE of generics cleanly, which means the type system is effectively useless in those scenarios and you have to write a bunch of “unchecked” code.
C# allows operator overloading and Java doesn’t, which makes working with mathy objects annoying (e.g. vectors)
C# has “async” and “await” keywords which make asynchronous code way easier to write and reason about.
//C#
private readonly HttpClient _httpClient = new HttpClient();
private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
// Capture the task handle here so we can await the background task later.
var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");
// Any other work on the UI thread can be done here, such as enabling a Progress Bar.
// This is important to do here, before the "await" call, so that the user
// sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;
// The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
// This is what allows the app to be responsive and not block the UI thread.
var html = await getDotNetFoundationHtmlTask;
int count = Regex.Matches(html, @"\.NET").Count;
DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
C# has null conditional and null coalescing operators so you can do things like this which will just return null if A or B are null:
// C#
A?.B?.Do(C);
C# has tuples and anonymous types.
//C#
(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
var v = new { Amount = 108, Message = "Hello" };
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);
C# has default and named arguments so you don’t have to make a million overloads for each number of arguments.
// C#
public void ExampleMethod(int required, string optionalstr = "default string",
int optionalint = 10)
{
Console.WriteLine(
$"{_name}: {required}, {optionalstr}, and {optionalint}.");
}
ExampleMethod(3, optionalint: 4);
C# has “extension methods” which are basically syntactic sugar that lets you “add” methods to classes you don’t own:
// C#
public static class StringExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// usage in some other context
string s = "this is a string";
int numWords = s.WordCount(); // => 4
C#'s “properties” are way nicer than java accessors. you can treat them like instance variables with dot syntax, and it’s less verbose to define them:
// C#
public class TimePeriod
{
private double _seconds;
public double Hours
{
get { return _seconds / 3600; }
set
{
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException(nameof(value),
"The valid range is between 0 and 24.");
_seconds = value * 3600;
}
}
}
TimePeriod t = new TimePeriod();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;
// Retrieving the property causes the 'get' accessor to be called.
Console.WriteLine($"Time in hours: {t.Hours}");
// The example displays the following output:
// Time in hours: 24
C# has interpolated strings while Java only has what C# calls “composite formatting”:
// C#
var name = "Mark";
var date = DateTime.Now;
// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
// Both calls produce the same output that is similar to:
// Hello, Mark! Today is Wednesday, it's 19:40 now.
C# has exception filters (the ‘when’ part of this statement):
//C#
try
{
SomeCall();
}
catch (ServerResponseException ex) when (ex.StatusCode == 500)
{
logger.log(ex);
}
C# has object and collection “initializers” which lets you do like an object literal syntax (having to set everything separately in Java drives me NUTS).
// C#
public class Cat
{
// Auto-implemented properties.
public int Age { get; set; }
public string? Name { get; set; }
public Cat()
{
}
public Cat(string name)
{
this.Name = name;
}
}
List<Cat> cats = new List<Cat>
{
new Cat{ Name = "Sylvester", Age=8 },
new Cat{ Name = "Whiskers", Age=2 },
new Cat{ Name = "Sasha", Age=14 }
};
var numbers = new Dictionary<int, string>
{
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};
there are a lot more examples i could do!!! but like all this culminates into C# feeling like it has “solutions” for common use cases and Java just, uh, doesn’t. the culture of Java encourages the heavy use of “patterns” because there is NOT a simple way to do a lot of common things, so they try to abstract that complexity away with a pattern while C# just has it as an OOTB feature (e.g. it’s already been abstracted for you!)