Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/CSharpVsJava.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpVsJava", "CSharpVsJava\CSharpVsJava.csproj", "{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Debug|x64.ActiveCfg = Debug|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Debug|x64.Build.0 = Debug|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Debug|x86.ActiveCfg = Debug|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Debug|x86.Build.0 = Debug|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Release|Any CPU.Build.0 = Release|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Release|x64.ActiveCfg = Release|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Release|x64.Build.0 = Release|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Release|x86.ActiveCfg = Release|Any CPU
{D563DC6E-1CA4-4A6B-9477-5F6C94F699AD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;

namespace CSharpVsJava;

public static class ConcurrencyDemo
{
public static async Task RunAsync()
{
var stopwatch = Stopwatch.StartNew();

var stock = await Task.WhenAll(Enumerable.Range(1, 10_000).Select(GetStockAsync));

Console.WriteLine($"{stock.Length:N0} stock checks in {stopwatch.ElapsedMilliseconds:N0} ms");
}

private static async Task<int> GetStockAsync(int productId)
{
await Task.Delay(TimeSpan.FromSeconds(1)); // stands in for a database or HTTP call
return productId % 7;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace CSharpVsJava;

public record Money(decimal Amount)
{
public static Money operator +(Money left, Money right) => new(left.Amount + right.Amount);
}

public class Product
{
public required string Name { get; init; }
public required Money Price { get; set; }
}

public static class EverydayDemo
{
public static void Run()
{
var lamp = new Product { Name = "Desk lamp", Price = new Money(30m) };
lamp.Price += new Money(5m);

Console.WriteLine($"{lamp.Name} costs {lamp.Price.Amount}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace CSharpVsJava;

public static class GenericsDemo
{
public static void Run()
{
var quantities = new List<int> { 3, 1, 2 };
var customers = new List<string> { "Ann", "Bob" };

Console.WriteLine(quantities.GetType());
Console.WriteLine(quantities.GetType() == customers.GetType());
Console.WriteLine(Describe(quantities));
}

private static string Describe<T>(List<T> items) => $"{items.Count} items of type {typeof(T).Name}";
}
24 changes: 24 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/LinqDemo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace CSharpVsJava;

public record Order(string Customer, int Quantity);

public static class LinqDemo
{
public static void Run()
{
var orders = new List<Order>
{
new("Ann", 3), new("Bob", 1), new("Ann", 4), new("Cid", 5), new("Bob", 2)
};

var totals = orders
.GroupBy(order => order.Customer)
.Select(group => new { Customer = group.Key, Items = group.Sum(order => order.Quantity) })
.OrderByDescending(total => total.Items);

foreach (var total in totals)
{
Console.WriteLine($"{total.Customer}: {total.Items}");
}
}
}
19 changes: 19 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/MemoryDemo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace CSharpVsJava;

public static class MemoryDemo
{
public static void Run()
{
const int count = 1_000_000;
var before = GC.GetAllocatedBytesForCurrentThread();

var quantities = new List<int>(count);
for (var i = 0; i < count; i++)
{
quantities.Add(i);
}

var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Console.WriteLine($"List<int> with {quantities.Count:N0} items: {allocated / 1_000_000.0:F1} MB allocated");
}
}
18 changes: 18 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/NullDemo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace CSharpVsJava;

public static class NullDemo
{
public static void RunUnchecked()
{
string? email = FindEmail(customerId: 42);
Console.WriteLine(email.Length);

Check warning on line 8 in csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/NullDemo.cs

View workflow job for this annotation

GitHub Actions / Build & test (csharp-intermediate-topics/CSharpVsJava)

Dereference of a possibly null reference.

Check warning on line 8 in csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/NullDemo.cs

View workflow job for this annotation

GitHub Actions / Build & test (csharp-intermediate-topics/CSharpVsJava)

Dereference of a possibly null reference.
}

public static void Run()
{
string? email = FindEmail(customerId: 42);
Console.WriteLine(email?.Length ?? 0);
}

private static string? FindEmail(int customerId) => customerId == 1 ? "ann@example.com" : null;
}
16 changes: 16 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using CSharpVsJava;

switch (args.FirstOrDefault())
{
case "generics": GenericsDemo.Run(); break;
case "memory": MemoryDemo.Run(); break;
case "concurrency": await ConcurrencyDemo.RunAsync(); break;
case "linq": LinqDemo.Run(); break;
case "shapes": ShapesDemo.Run(); break;
case "nulls": NullDemo.Run(); break;
case "nulls-unchecked": NullDemo.RunUnchecked(); break;
case "everyday": EverydayDemo.Run(); break;
default:
Console.WriteLine("Pass one of: generics, memory, concurrency, linq, shapes, nulls, nulls-unchecked, everyday");
break;
}
27 changes: 27 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/CSharpVsJava/ShapesDemo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace CSharpVsJava;

public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Square(double Side) : Shape;

public static class ShapesDemo
{
public static void Run()
{
Shape[] shapes = [new Circle(1), new Square(2)];
foreach (var shape in shapes)
{
Console.WriteLine($"{shape}: {Area(shape):F2}");
}

var bigger = new Circle(1) with { Radius = 3 };
Console.WriteLine(bigger);
}

private static double Area(Shape shape) => shape switch
{
Circle(var radius) => Math.PI * radius * radius,
Square(var side) => side * side,
_ => throw new ArgumentOutOfRangeException(nameof(shape))
};
}
42 changes: 42 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# C# vs Java: Which One Should You Pick?

Sample code for the Code Maze article [C# vs Java: Which One Should You Pick?](https://code-maze.com/csharp-vs-java/).

Each difference the article shows is one small program in both languages:

| Demo | C# (console app) | Java (single-file program) |
|---|---|---|
| Generics at runtime | `GenericsDemo.cs` | `java/Generics.java` |
| Memory for a million numbers | `MemoryDemo.cs` | `java/Memory.java` |
| 10,000 one-second waits | `ConcurrencyDemo.cs` | `java/Concurrency.java` |
| Grouping query | `LinqDemo.cs` | `java/Orders.java` |
| Records and pattern matching | `ShapesDemo.cs` | `java/Shapes.java` |
| Null safety | `NullDemo.cs` | `java/Nulls.java` |
| Properties, operators, checked exceptions | `EverydayDemo.cs` | `java/Everyday.java` |

## Running the C# demos

You need the .NET 10 SDK. From this folder, pass the name of one demo:

```
dotnet run --project CSharpVsJava -- generics
```

The names are `generics`, `memory`, `concurrency`, `linq`, `shapes`, `nulls`, `nulls-unchecked` and `everyday`.

The build prints one warning on purpose, CS8602 in `NullDemo.RunUnchecked()`, and `nulls-unchecked` ends in a `NullReferenceException`. That is the bug the article asks the compiler to catch.

## Running the Java programs

You need JDK 25 or later. Each file runs on its own, with no Maven or Gradle project:

```
cd java
java Generics.java
```

`Nulls.java` ends in a `NullPointerException` (exit code 1) on purpose, and `Everyday.java` expects no `orders.csv` in the folder it runs from.

## A note on CI

The repository's CI builds this folder through `CSharpVsJava.sln`, so it compiles the C# project only. It does not compile or run the files in `java/`. They are here so the reader can run both sides, and we ran them with Eclipse Temurin 25 when we wrote the article.
20 changes: 20 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Concurrency.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
void main() throws Exception {
long start = System.nanoTime();

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var checks = IntStream.rangeClosed(1, 10_000)
.mapToObj(id -> executor.submit(() -> getStock(id)))
.toList();
for (var check : checks) {
check.get();
}
}

long elapsed = Duration.ofNanos(System.nanoTime() - start).toMillis();
IO.println("%,d stock checks in %,d ms".formatted(10_000, elapsed));
}

int getStock(int productId) throws InterruptedException {
Thread.sleep(Duration.ofSeconds(1)); // stands in for a database or HTTP call
return productId % 7;
}
31 changes: 31 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Everyday.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
record Money(BigDecimal amount) {
Money add(Money other) {
return new Money(amount.add(other.amount));
}
}

class Product {
private final String name;
private Money price;

Product(String name, Money price) {
this.name = name;
this.price = price;
}

String getName() { return name; }
Money getPrice() { return price; }
void setPrice(Money price) { this.price = price; }
}

void main() {
var lamp = new Product("Desk lamp", new Money(new BigDecimal("30")));
lamp.setPrice(lamp.getPrice().add(new Money(new BigDecimal("5"))));
IO.println(lamp.getName() + " costs " + lamp.getPrice().amount());

try {
IO.println(Files.readString(Path.of("orders.csv")));
} catch (IOException e) {
IO.println("Could not read " + e.getMessage());
}
}
12 changes: 12 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Generics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
void main() {
var quantities = new ArrayList<Integer>(List.of(3, 1, 2));
var customers = new ArrayList<String>(List.of("Ann", "Bob"));

IO.println(quantities.getClass());
IO.println(quantities.getClass() == customers.getClass());
IO.println(describe(quantities, Integer.class));
}

<T> String describe(List<T> items, Class<T> type) {
return items.size() + " items of type " + type.getSimpleName();
}
17 changes: 17 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Memory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import com.sun.management.ThreadMXBean;
import java.lang.management.ManagementFactory;

void main() {
final int count = 1_000_000;
var threads = (ThreadMXBean) ManagementFactory.getThreadMXBean();
long before = threads.getCurrentThreadAllocatedBytes();

var quantities = new ArrayList<Integer>(count);
for (int i = 0; i < count; i++) {
quantities.add(i);
}

long allocated = threads.getCurrentThreadAllocatedBytes() - before;
IO.println("ArrayList<Integer> with %,d items: %.1f MB allocated"
.formatted(quantities.size(), allocated / 1_000_000.0));
}
14 changes: 14 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Nulls.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
void main() {
IO.println(findEmail(42).map(String::length).orElse(0));

String email = findEmailOrNull(42);
IO.println(email.length());
}

Optional<String> findEmail(int customerId) {
return customerId == 1 ? Optional.of("ann@example.com") : Optional.empty();
}

String findEmailOrNull(int customerId) {
return customerId == 1 ? "ann@example.com" : null;
}
13 changes: 13 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Orders.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
record Order(String customer, int quantity) {}

void main() {
var orders = List.of(
new Order("Ann", 3), new Order("Bob", 1), new Order("Ann", 4),
new Order("Cid", 5), new Order("Bob", 2));

orders.stream()
.collect(Collectors.groupingBy(Order::customer, Collectors.summingInt(Order::quantity)))
.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.forEach(total -> IO.println(total.getKey() + ": " + total.getValue()));
}
17 changes: 17 additions & 0 deletions csharp-intermediate-topics/CSharpVsJava/java/Shapes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}

void main() {
List<Shape> shapes = List.of(new Circle(1), new Square(2));
for (var shape : shapes) {
IO.println("%s: %.2f".formatted(shape, area(shape)));
}
}

double area(Shape shape) {
return switch (shape) {
case Circle(double radius) -> Math.PI * radius * radius;
case Square(double side) -> side * side;
};
}
Loading