Java.equals(C#)?
So you skipped. You skipped all of the previous parts where I talked about some important differences. But you're allowed to continue reading from this point, just don't butthurt too much, because this part is focusing solely on language features:
Tuples
Let's start with a simple one. In C# you can use (int x, int y)
instead of any type anywhere and get over with it. In Java it's different, so I want you to check the example:
public (string name, int age) GetAge()
{
return (name: "Aboba", age: 54);
}
import javafx.util.Pair;
public Pair<String, Integer> getAge() {
return new Pair<>("Aboba", 54);
}
Basically, it's the same, but only almost the same!
️Nullability
Just watch this Billion dollar mistake by Tony Hoare - creator of the ALGOL programming language, where he introduced this null value into every type. He thought, at the time, that this was very convenient, but it turned out to be very problematic in the future with NullReferenceException being THE MOST COMMON ERROR IN THE WORLD.
So how to solve it? Well, just split types that can have null values and the ones that can't. In C#, for example, you can enable a special mode and use string?
type - it basically means string + null
. And you're required to check for null
everywhere it is used.
In Java a similar thing is used, but it's more optional. You'll need to annotate a type that you want to make nullable with @Nullable
. But it's, honestly, more tedious to write.
️Properties
I've always found it weird how Java does all these shenanigans with its getters and setters when in C# you can just do the:
class Point
{
public int X { get; set; }
public int Y { get; set; }
}
So to access one of the coordinates, just use p.X
and it will work like a charm! Because property is a function, that behaves like a value. You can also customize all these get and set methods to make them do what you want:
class Point
{
public int _x;
public int X
{
get => _x,
private set
{
Console.WriteLine("We're setting X, this must be some occasion");
_x = value;
}
}
public int Y { get; set; }
}
So it's quite powerful syntactic sugar here!
Dynamic
Let's get this straight. Java doesn't directly support this stuff. But you can overcome this. Continue.
dynamic
keyword in C# is useful in situations where the type of an object or variable is not known until runtime. With the dynamic keyword, developers can write code that can adapt to different types at runtime. This provides more flexibility and ease of use.
For example, suppose we have an external data source that provides data in JSON format. We can use dynamic
to deserialize the data without knowing the exact structure of the data beforehand. The following code snippet shows how dynamic
keyword can be used to deserialize JSON data:
using Newtonsoft.Json;
dynamic data = JsonConvert.DeserializeObject(jsonString);
var (name, age, hobbies) = (data.name, data.age, data.hobbies);
For this feature to be available there's a whole new dynamic runtime involved! But I've heard rumors that C# dynamic
keyword is really a bad practice and there's a new thing going out(unfortunately I don't have a link for this).
LINQ n' stuff
This one is my favorite! LINQ (Language Integrated Query) is a feature in C# that provides a consistent way to query data from various sources. It works by using a set of standard query operators to manipulate data in a declarative way.
using System;
using System.Collections.Generic;
using System.Linq;
public static class Extensions
{
// Custom extension method that returns a sequence of distinct numbers
public static IEnumerable<int> DistinctNumbers(this IEnumerable<int> source)
{
// Use LINQ's Distinct method to get unique numbers
return source.Distinct();
}
}
class Program
{
static void Main(string[] args)
{
// Create a list of numbers
List<int> numbers = new List<int>() { 1, 2, 2, 3, 3, 3 };
// Use the custom DistinctNumbers method to get unique numbers
var distinctNumbers = numbers.DistinctNumbers();
// Use LINQ's Sum method to get the sum of the numbers
var sum = numbers.Sum();
// Output the results
Console.WriteLine("Original numbers: " + string.Join(", ", numbers));
Console.WriteLine("Distinct numbers: " + string.Join(", ", distinctNumbers));
Console.WriteLine("Sum: " + sum);
}
}
Its design is certainly an example of wise design. But the development of this feature was not instant. It was a few years of constant small language improvements before they were all pushed for LINQ to emerge in the end.
Here are the main parts that made up LINQ historically:
Lambdas
Lambda expressions are used to create anonymous functions that can be passed as arguments to methods or stored as variables. In LINQ, lambdas are commonly used to define the predicates and projections that are applied to data sets.
int[] numbers = { 1, 2, 3, 4, 5 };
var filteredNumbers = numbers.Where(n => n % 2 == 0);
Generics
Generics are used extensively in LINQ to create type-safe query operators that can be applied to different types of data.
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (T item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
IEnumerable
The IEnumerable
interface is used as the base type for all LINQ query results. It provides a standard way to iterate over data sets and supports lazy evaluation of query results.
IEnumerable<int> numbers = Enumerable.Range(1, 5);
foreach (int number in numbers)
{
Console.WriteLine(number);
}
Laziness
LINQ uses lazy evaluation to defer the execution of query operations until the results are actually needed. This can improve performance and reduce memory usage, especially when working with large data sets.
var numbers = Enumerable.Range(1, 10);
var evenNumbers = numbers.Where(n => n % 2 == 0);
var firstFiveEvenNumbers = evenNumbers.Take(5);
foreach (int number in firstFiveEvenNumbers)
{
Console.WriteLine(number);
}
Yield
The yield
keyword is used in LINQ to define iterator methods that can be used to generate sequences of data on the fly. This is useful for implementing query operators that operate on large or infinite data sets.
public static IEnumerable<int> GenerateRandomNumbers(int count)
{
Random random = new Random();
for (int i = 0; i < count; i++)
{
yield return random.Next();
}
}
Query syntax
The query syntax in LINQ provides a more natural way to express queries using SQL-like syntax. It is translated into method calls behind the scenes and provides a more readable way to write complex queries.
var numbers = from n in Enumerable.Range(1, 10)
where n % 2 == 0
select n;
foreach (int number in numbers)
{
Console.WriteLine(number);
}
Value types
In C#, both struct
s and class
es are used to define custom data types, but they have some differences. Structs are value types, whereas classes are reference types.
When a struct
is assigned to a variable or passed as a parameter, its value is copied to the new location. This means that changes to the variable or parameter do not affect the original value. In contrast, when a class is assigned to a variable or passed as a parameter, only a reference to the object is copied. This means that changes to the variable or parameter affect the original object.
struct
s are typically used for small data types that have value semantics, while class
es are used for larger data types that require reference semantics.
Local functions
They both support this (finally)! See the example in Java:
public void outerMethod() {
System.out.println("This is the outer method.");
// Nested method
void innerMethod() {
System.out.println("This is the inner method.");
}
// Call the nested method
innerMethod();
}
This fights the creation of a lot of stupid __do_helper_thing
methods.
Partial classes
In C#, a partial class is a class that can be split into multiple files, while still being treated as a single class by the compiler. This feature allows developers to organize a class's implementation across multiple files, making it easier to manage large and complex classes.
To create a partial class, simply add the partial
keyword to the class declaration in each file:
public partial class MyClass {
public void Method1() {
// implementation
}
}
public partial class MyClass {
public void Method2() {
// implementation
}
}
In this example, both files define the same class MyClass
, but with different methods. When compiled, the two files will be merged into a single class definition with both Method1
and Method2
included:
public partial class MyClass {
public void Method1() {
// implementation
}
public void Method2() {
// implementation
}
}
Partial classes can be especially useful when working with code generated by tools or frameworks, as they allow developers to add their own customizations and extensions to the generated code without modifying the original source files.
Also, this works with partial methods.
Anonymous types
In C#, anonymous types are a way to create a new class with read-only properties on the fly, without explicitly defining a class. Anonymous types are useful for creating objects to store and manipulate data that is not intended to be reused in other parts of the program.
var person = new { Name = "John", Age = 30 };
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
In Java anonymous classes is something similar yet different. They are a way to create a subclass or an implementation of an interface without explicitly defining a new class. Anonymous classes are useful when you need to create a one-time-use class to provide a specific implementation of a method or an interface.
interface MyInterface {
void doSomething();
}
public class MyClass {
public static void main(String[] args) {
MyInterface obj = new MyInterface() {
public void doSomething() {
System.out.println("Doing something...");
}
};
obj.doSomething();
}
}
Either way, comparing different approaches to similar things in these languages helps us detect the design space limits and create better software, giving more economical output! Good.
Other
There are also parts of languages that weren't covered here:
This was mainly applause on C# side. But actually, it was the subjective reality of a C# dev that has encountered some Java once in a while. I hope this goes better over time as I promise to update the page every now and then when I'll get new input on Java's part.
Have a good time!
Member discussion