// import System.*;
using System;
// package Tutorial;
namespace Tutorial
{
class Program
{ // main String
static void Main(string[] args) // private (!)
{
// System.out.println
Console.WriteLine("Hello World!");
}
}
}
- 1995 Sun releases Java 1.0
- 1996 Microsoft releases Visual J++
- proprietary, incompatible Java implementation
- 1998 Sun files civil lawsuit against Microsoft
- 2000 Anders Hejlsberg begins work on C#
- Turbo Pascal
- Delphi
- TypeScript
2002 | 2005 | 2008 | 2010 | 2012 | 2015 | 2017 | 2019 |
---|---|---|---|---|---|---|---|
Properties | Generics | Named & | async |
Tuples | |||
Delegates | foreach |
optional | await |
Destructuring | |||
Events | yield |
arguments | Roslyn | T? |
|||
interface |
Lambdas | Pattern | |||||
class |
var |
dynamic |
matching | ||||
struct |
LINQ |
- 2001 .NET Framework (Windows)
- 2004 Mono (Windows, macOS, Linux)
- 2016 .NET Core (Windows, macOS, Linux)
- 2020 retire .NET Framework
- 2020 rename .NET Core to .NET
Java | C# | Minimum | Maximum |
---|---|---|---|
boolean |
bool |
false |
true |
char |
char |
'\u0000' |
'\uFFFF' |
byte |
sbyte |
-128 | +127 |
byte |
0 | 255 | |
short |
short |
-32,768 | +32,767 |
ushort |
0 | 65,535 | |
int |
int |
-2,147,483,648 | +2,147,483,647 |
uint |
0 | 4,294,967,295 | |
long |
long |
-9,223,372,036,854,775,808 | +9,223,372,036,854,775,807 |
ulong |
0 | 18,446,744,073,709,551,615 | |
float |
float |
1.4 * 10^ -45 | 3.4 * 10^ +38 |
double |
double |
4.9 * 10^-324 | 1.8 * 10^+308 |
using System;
namespace Tutorial
{
class K
{
public int balance;
}
struct S
{
public int balance;
}
class Program
{
static void Main(string[] args)
{
K a = new K();
K b = a;
a.balance = 97;
Console.WriteLine(b.balance); // 97
S c = new S();
S d = c;
c.balance = 97;
Console.WriteLine(d.balance); // 0
}
}
}
class
variable stores object reference (ornull
)- Assignment copies object reference
struct
variable stores struct directly- Assignment copies entire struct
using System;
namespace Tutorial
{
class Program
{
static void swap(object a, object b)
{
Console.WriteLine($"{a} {b}"); // world hello
object c;
c = a;
a = b;
b = c;
Console.WriteLine($"{a} {b}"); // hello world
}
static void Main(string[] args)
{
string s = "world";
string t = "hello";
Console.WriteLine($"{s} {t}"); // world hello
swap(s, t);
Console.WriteLine($"{s} {t}"); // world hello
}
}
}
- Value parameters are just copies of their respective arguments
- In the above example, the object references are copied
a
ands
share the same string object
- Assignments to value parameters are invisible to the caller
a
is rebound, buts
is not
- Mutation through
a
would be noticeable throughs
- But
object
has no mutator methods - And
string
s are immutable
- But
ref
parameters alias their respective argument variables:
using System;
namespace Tutorial
{
class Program
{
static void swap(ref object a, ref object b)
{
object c;
c = a;
a = b;
b = c;
}
static void Main(string[] args)
{
string s = "world";
string t = "hello";
// cannot convert from "ref string" to "ref object"
swap(ref s, ref t);
}
}
}
- In expected OOP fashion,
string
s areobject
s - But
string
variables are notobject
variables- Otherwise, we could rebind
string
variables to non-string
objects:
- Otherwise, we could rebind
string s = "hi";
ref object o = ref s; // cannot convert from "ref string" to "ref object"
o = 42;
- Either hardwire
swap
forstring
variables:
static void swap(ref string a, ref string b)
{
string c;
c = a;
a = b;
b = c;
}
- Or generify
swap
:
static void swap<T>(ref T a, ref T b)
{
T c;
c = a;
a = b;
b = c;
}
- Properties are accessed like fields
- But they are implemented with
get
/set
methods
using System;
namespace Tutorial
{
struct Duration
{
// Constants are not stored anywhere at runtime.
// Usages compile away to their actual value.
public const int SECONDS_PER_MINUTE = 60;
public const int SECONDS_PER_HOUR = 60 * 60;
private long _seconds;
public long Seconds
{
get => _seconds;
set => _seconds = value;
}
public long Minutes
{
get
{
return _seconds / SECONDS_PER_MINUTE;
}
set
{
if (value * SECONDS_PER_MINUTE / SECONDS_PER_MINUTE != value)
throw new ArgumentException($"{value} is too big");
_seconds = value * SECONDS_PER_MINUTE;
}
}
public long Hours
{
get => _seconds / SECONDS_PER_HOUR;
set
{
if (value * SECONDS_PER_HOUR / SECONDS_PER_HOUR != value)
throw new ArgumentException($"{value} is too big");
_seconds = value * SECONDS_PER_HOUR;
}
}
}
class Program
{
static void Main(string[] args)
{
Duration dur = new Duration();
dur.Hours = 2;
// 120 minutes is 7200 seconds
Console.WriteLine($"{dur.Minutes} minutes is {dur.Seconds} seconds");
}
}
}
extends C implements I, J, K
translates to: C, I, J, K
- The default is
: object
- The default is
super(...);
orthis(...);
translate to: base(...)
or: this(...)
- The default is
: base()
- The default is
- Default constructor is generated in the absence of explicit constructors
- Only
abstract
andvirtual
methods are overridable - Overriding requires
override
keyword- Missing
override
does not compile, unlike Java@Override
override
interface method not compile, unlike Java@Override
- Missing
using System;
namespace Tutorial
{
abstract class AbstractList // : object
{
// public AbstractList() : base() {}
public abstract int Count();
public virtual bool IsEmpty()
{
return Count() == 0;
}
}
class ArrayList : AbstractList
{
private object[] data;
private int count;
public ArrayList() // : base()
{
data = new object[2];
count = 0;
}
public override int Count()
{
return count;
}
public void Add(object element)
{
int capacity = data.Length;
if (count == capacity)
{
capacity += capacity / 2;
Array.Resize(ref data, capacity);
}
data[count++] = element;
}
public object Get(int index)
{
return data[index];
}
public void Set(int index, object element)
{
data[index] = element;
}
}
class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList();
al.Add("hello");
al.Add("world");
al.Add("and");
al.Add("then");
al.Add("goodbye");
al.Set(0, "hi");
Console.WriteLine(al.Get(4));
}
}
}
using System;
namespace Tutorial
{
abstract class AbstractList
{
public abstract int Count { get; }
public virtual bool IsEmpty()
{
return Count == 0;
}
}
class ArrayList : AbstractList
{
private object[] data;
private int count;
public ArrayList()
{
data = new object[2];
count = 0;
}
public override int Count
{
get => count;
}
public void Add(object element)
{
int capacity = data.Length;
if (count == capacity)
{
capacity += capacity / 2;
Array.Resize(ref data, capacity);
}
data[count++] = element;
}
public object this[int index]
{
get => data[index];
set => data[index] = value;
}
}
class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList();
al.Add("hello");
al.Add("world");
al.Add("and");
al.Add("then");
al.Add("goodbye");
al[0] = "hi";
Console.WriteLine(al[4]);
}
}
}
Java | C# |
---|---|
Iterable |
IEnumerable |
List |
IList |
ArrayList |
List |
LinkedList |
LinkedList |
Set |
ISet |
HashSet |
HashSet |
TreeSet |
SortedSet |
Map |
IDictionary |
HashMap |
Dictionary |
TreeMap |
SortedDictionary |
Comparable |
IComparable |
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Tutorial
{
class Program
{
static IEnumerable<BigInteger> fibonacciSequence()
{
BigInteger a = 0;
yield return a;
BigInteger b = new BigInteger(1);
yield return b;
while (true)
{
BigInteger c = a + b;
yield return c;
a = b;
b = c;
}
}
static void Main(string[] args)
{
foreach (BigInteger fib in fibonacciSequence())
{
if (fib >= 1000) break;
Console.WriteLine(fib);
// 0
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
// 55
// 89
// 144
// 233
// 377
// 610
// 987
}
}
}
}
using System.Linq;
static void Main(string[] args)
{
foreach (BigInteger fib in fibonacciSequence().TakeWhile(x => x < 1000))
{
Console.WriteLine(fib);
}
}
IEnumerable<string> adultNames = people.Where(p => p.Age >= 18).Select(p => p.Name);
IEnumerable<string> adultNames = from p in people
where p.Age >= 18
select p.Name;
- Note the code duplication in
fixCos
andfixSqrt
:
using System;
using System.Collections.Generic;
namespace Tutorial
{
class Program
{
static IEnumerable<double> fixCos()
{
double x = 0;
do
{
yield return x;
} while (x != (x = Math.Cos(x)));
}
static IEnumerable<double> fixSqrt()
{
double x = 0.5;
do
{
yield return x;
} while (x != (x = Math.Sqrt(x)));
}
static void Main(string[] args)
{
foreach (double x in fixCos())
{
Console.WriteLine(x);
// 0
// 1
// 0.5403023058681397
// 0.8575532158463934
// 0.6542897904977791
// 0.7934803587425656
// 0.7013687736227565
// ...
// 0.7390851332151608
// 0.7390851332151606
// 0.7390851332151607
}
foreach (double x in fixSqrt())
{
Console.WriteLine(x);
// 0.5
// 0.7071067811865476
// 0.8408964152537146
// 0.9170040432046712
// 0.9576032806985736
// 0.9785720620877001
// 0.9892280131939755
// ...
// 0.9999999999999997
// 0.9999999999999998
// 0.9999999999999999
}
}
}
}
- Can we refactor
fixCos
andfixSqrt
into one method?- Abstract over initial number (
0
or0.5
) withdouble x
parameter - Abstract over applied method (
Math.Cos
orMath.Sqrt
) with delegate parameter:
- Abstract over initial number (
using System;
using System.Collections.Generic;
namespace Tutorial
{
class Program
{
delegate double F(double x);
static IEnumerable<double> fix(F f, double x)
{
do
{
yield return x;
} while (x != (x = f(x)));
}
static void Main(string[] args)
{
foreach (double x in fix(Math.Cos, 0))
{
Console.WriteLine(x);
}
foreach (double x in fix(Math.Sqrt, 0.5))
{
Console.WriteLine(x);
}
}
}
}
Func
tion delegates are already provided by the standard library:Func<TResult>
Func<T, TResult>
Func<T1, T2, TResult>
Func<T1, T2, T3, TResult>
- ...
Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>
static IEnumerable<double> fix(Func<double, double> f, double x)
- Also
Action
s forvoid
result types:Action
Action<T>
Action<T1, T2>
Action<T1, T2, T3>
- ...
Action<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
- Delegates can store (multiple!) observers:
using System;
using System.Threading;
namespace Tutorial
{
class SubjectExample
{
public Action<DateTime> HellFrozeOver;
public void FreezeHell()
{
Console.Write("Freezing hell... ");
Thread.Sleep(2000);
Console.WriteLine("done!");
if (HellFrozeOver != null)
{
HellFrozeOver(DateTime.Now);
}
}
}
class ObserverExample
{
public void Log(DateTime when)
{
Console.WriteLine("Hell froze over on " + when);
}
}
class Program
{
static void Main(string[] args)
{
var subject = new SubjectExample();
var first = new ObserverExample();
var second = new ObserverExample();
subject.HellFrozeOver += first.Log;
subject.HellFrozeOver += second.Log;
subject.FreezeHell();
Console.WriteLine("--------------------------------------");
var third = new ObserverExample();
// Whoops, accidental = instead of +=
subject.HellFrozeOver = third.Log;
// Whoops, delegate invocation outside of subject
subject.HellFrozeOver(new DateTime(1912, 4, 15));
}
}
}
- The
event
keyword prevents such accidental misuse:
class SubjectExample
{
public event Action<DateTime> HellFrozeOver;
public void FreezeHell()
{
// ...
}
}
// The event SubjectExample.HellFrozeOver can only appear
// on the left hand side of += or -=
// (except when used from within the type SubjectExample)
subject.HellFrozeOver = third.Log;
// ditto
subject.HellFrozeOver(new DateTime(1912, 4, 15));