Difference between revisions of "Programming in D for CSharp Programmers"
(→Struct) |
(Undo revision 10004 by Ceydacoder (talk)) (Tag: Undo) |
||
(61 intermediate revisions by 8 users not shown) | |||
Line 3: | Line 3: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | file: hello.cs | |
− | + | <syntaxhighlight lang="C#"> | |
− | + | using System; | |
− | + | class Hello | |
− | + | { | |
− | + | static void Main(string[] args) | |
+ | { | ||
+ | Console.WriteLine("Hello world!"); | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | file: hello.d | |
− | + | <syntaxhighlight lang="D"> | |
− | + | import std.stdio; | |
− | + | void main(string[] args) | |
+ | { | ||
+ | writeln("Hello world!"); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | Things we learnt so far: | + | Things we have learnt so far: |
* standard file extension for D source code files is <code>.d</code> | * standard file extension for D source code files is <code>.d</code> | ||
* we <code>import</code> a module instead of <code>using</code> a namespace; | * we <code>import</code> a module instead of <code>using</code> a namespace; | ||
− | * | + | * methods (functions) can be top-level, that is, not declared inside a class; and called without being prefixed with an object/class or class name (e.g. <code>writeln</code>) |
− | + | * <code>writeln</code> is the D equivalent for C#'s <code>System.Console.WriteLine</code>; | |
− | * <code>writeln</code> is D equivalent for C# <code>System.Console.WriteLine</code>; | + | * some syntax is exactly the same as in C# (method definitions, string qualifiers, array declarations, comments) |
− | * syntax is exactly the same as in C# (method definitions, string qualifiers, array declarations, comments) | ||
* many of the keywords are exactly the same (<code>void,string</code>); | * many of the keywords are exactly the same (<code>void,string</code>); | ||
= Documentation Comments = | = Documentation Comments = | ||
''The C# Way'' | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
/// <summary> | /// <summary> | ||
/// Documentation comment | /// Documentation comment | ||
Line 35: | Line 44: | ||
/// <param name="bar">...</param> | /// <param name="bar">...</param> | ||
/// <returns>...</returns> | /// <returns>...</returns> | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
/** | /** | ||
− | + | Documentation comment | |
− | + | ||
− | + | Params: foo = param description | |
− | + | bar = param description | |
− | + | ||
− | + | Returns: return value | |
− | + | ||
− | + | Throws: Exception | |
*/ | */ | ||
+ | </syntaxhighlight> | ||
= Compilation = | = Compilation = | ||
Line 67: | Line 79: | ||
-define:S1[;S2] | -define:S1[;S2] | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | // compile with: /define:xx | |
− | + | // or uncomment the next line: | |
− | + | //#define xx | |
− | + | [Test] public void define_test() { | |
int x = 0; | int x = 0; | ||
#if (xx) | #if (xx) | ||
Line 80: | Line 92: | ||
#endif | #endif | ||
Assert.AreEqual(x, 5); | Assert.AreEqual(x, 5); | ||
− | + | } | |
− | + | </syntaxhighlight> | |
''The D Way'' | ''The D Way'' | ||
− | + | -version=S1 | |
+ | <syntaxhighlight lang="D"> | ||
+ | // compile with: -version=xx | ||
+ | // or uncomment the next line: | ||
+ | //version = xx | ||
+ | |||
+ | unittest { | ||
+ | int x = 0; | ||
+ | version(xx) { | ||
+ | x = 5; | ||
+ | } | ||
+ | else { | ||
+ | x = 1; | ||
+ | } | ||
+ | assert( x == 5 ); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
= Coding styles = | = Coding styles = | ||
Line 145: | Line 173: | ||
== Literals == | == Literals == | ||
− | ''TO DO'' | + | '''TO DO''' |
== Arrays == | == Arrays == | ||
− | Arrays in D are not too different than the ones form C# (for D see [https://github.com/ilmanzo/DLangKoans/blob/master/source/ | + | Arrays in D are not too different than the ones form C# (for D see [https://github.com/ilmanzo/DLangKoans/blob/master/source/07_about_arrays.d D Koans] and [http://dlang.org/arrays.html#static-arrays dlang]) |
=== Static array === | === Static array === | ||
==== Initialize with a literal ==== | ==== Initialize with a literal ==== | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | string[] fruits = {"banana", "mango", "apple", "orange"}; | |
− | + | // or long mode | |
− | + | string[] longFruits = new string[4]{"banana", "mango", "apple", "orange"}; | |
− | + | Assert.AreEqual(fruits[0], "banana"); | |
+ | Assert.AreEqual(fruits.Length, 4); | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | string[4] fruits = ["banana", "mango", "apple", "orange"]; | |
− | + | assertEquals(fruits[0], "banana"); | |
+ | assertEquals(fruits.length, 4); | ||
+ | </syntaxhighlight> | ||
==== Initialize with same value ==== | ==== Initialize with same value ==== | ||
''The C# Way'' | ''The C# Way'' | ||
− | |||
− | |||
+ | <syntaxhighlight lang="C#"> | ||
+ | // no short way | ||
+ | int[] b = { 1, 1, 1}; // 3 elements with same value 1 | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
+ | int[3] b = 1; // 3 elements with same value 1 | ||
+ | </syntaxhighlight> | ||
=== Dynamic array === | === Dynamic array === | ||
− | For D see [https://github.com/ilmanzo/DLangKoans/blob/master/source/ | + | For D see [https://github.com/ilmanzo/DLangKoans/blob/master/source/07_about_arrays.d D Koans] and [http://dlang.org/arrays.html#static-arrays dlang] |
''The C# Way'' | ''The C# Way'' | ||
− | List<T> | + | List<T> behaves similarly to a dynamic array |
− | + | <syntaxhighlight lang="C#"> | |
− | + | using System.Collections.Generic; | |
− | + | ... | |
− | + | List<string> fruits = new List<string>{"banana", "mango"}; | |
+ | Assert.AreEqual(fruits.Count, 2); | ||
− | + | fruits.Add("strawberry"); | |
− | + | Assert.AreEqual(fruits.Count, 3); | |
− | + | Assert.AreEqual(fruits[2], "strawberry"); | |
− | + | </syntaxhighlight> | |
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | string[] fruits = ["banana", "mango"]; | |
+ | assertEquals(fruits.length, 2); | ||
− | + | fruits ~= "strawberry"; | |
− | + | assertEquals(fruits.length, 3); | |
− | + | assertEquals(fruits[2], "strawberry"); | |
+ | </syntaxhighlight> | ||
== Pointers == | == Pointers == | ||
Line 201: | Line 240: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | int value; | |
− | + | // here you can't use pointers | |
− | int* p = & | + | unsafe { |
− | + | int* p = &value; | |
− | + | } | |
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | int value; | |
− | + | int* p = &value; | |
− | + | @safe { | |
//here you can't use pointers | //here you can't use pointers | ||
− | + | } | |
+ | </syntaxhighlight> | ||
== Delegates == | == Delegates == | ||
Line 220: | Line 261: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | delegate int F | |
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | int delegate(int x) Foo; | |
− | + | int function(int x) Foo; | |
+ | </syntaxhighlight> | ||
Since D doesn't need to declare methods inside a class, you can declare also a function, equivalent to a delegate without class context. A notable difference between C# and D is the fact that '''delegates are not multicast in D''', therefore you cannot join or remove them. | Since D doesn't need to declare methods inside a class, you can declare also a function, equivalent to a delegate without class context. A notable difference between C# and D is the fact that '''delegates are not multicast in D''', therefore you cannot join or remove them. | ||
Line 233: | Line 276: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
+ | enum Option | ||
+ | { | ||
Option1, | Option1, | ||
Option2 | Option2 | ||
− | + | } | |
− | + | [Flags] | |
− | + | enum Permission | |
+ | { | ||
read, | read, | ||
write, | write, | ||
all = read | wite | all = read | wite | ||
− | + | } | |
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
The members of enums should be camelCased, so their first letter is lowercase. (see [http://dlang.org/dstyle.html D Style] | The members of enums should be camelCased, so their first letter is lowercase. (see [http://dlang.org/dstyle.html D Style] | ||
− | + | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | enum Color | ||
+ | { | ||
red, | red, | ||
green, | green, | ||
blue | blue | ||
− | + | } | |
− | + | enum Permission | |
+ | { | ||
read, | read, | ||
write, | write, | ||
all = read | wite | all = read | wite | ||
− | + | } | |
+ | </syntaxhighlight> | ||
== Struct == | == Struct == | ||
Line 267: | Line 319: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
+ | public struct TimeOfDay { | ||
public int hour; | public int hour; | ||
public int minute; | public int minute; | ||
Line 274: | Line 327: | ||
minute = m; | minute = m; | ||
} | } | ||
− | + | } | |
− | + | ... | |
+ | |||
+ | var t1 = new TimeOfDay(8, 30); | ||
+ | Assert.AreEqual(t1.minute, 30); | ||
− | + | // Declare a struct object without "new" | |
− | + | TimeOfDay t2; | |
− | + | t2.minute = 10; | |
− | + | Assert.AreEqual(t2.minute, 10); | |
− | + | </syntaxhighlight> | |
− | |||
− | |||
− | |||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
+ | struct TimeOfDay { | ||
int hour; | int hour; | ||
int minute; | int minute; | ||
− | + | } | |
− | + | ... | |
− | + | // preferred syntax | |
− | + | auto t1 = TimeOfDay(8, 30); | |
− | + | assert(t1.hour == 8); | |
+ | assert(t1.minute == 30); | ||
− | + | // alternate C syntax, deprecated | |
− | + | TimeOfDay t2 = {9, 45}; | |
− | + | assert(t2.hour == 9); | |
− | + | // with designated initializers | |
− | + | auto t3 = {minute: 20, hour: 8}; | |
+ | assert(t3.hour == 8); | ||
+ | assert(t3.minute == 20); | ||
+ | |||
+ | auto t4 = TimeOfDay(10); // not all members need to be specified | ||
+ | assert(t4.hour == 10); | ||
+ | assert(t4.minute == 0); | ||
+ | </syntaxhighlight> | ||
== Union == | == Union == | ||
Line 307: | Line 369: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | [StructLayout(LayoutKind.Explicit)] | |
− | + | struct MyUnion { | |
[FieldOffset(0)] | [FieldOffset(0)] | ||
int someInt; | int someInt; | ||
[FieldOffset(0)] | [FieldOffset(0)] | ||
float someFloat; | float someFloat; | ||
− | + | } | |
− | + | </syntaxhighlight> | |
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | union MyUnion { | |
int someInt; | int someInt; | ||
float someFloat; | float someFloat; | ||
− | + | } | |
− | + | </syntaxhighlight> | |
== Interfaces == | == Interfaces == | ||
− | Interfaces are declared in the same way as in C#, the only difference being that interfaces in D can have final methods, equivalent to C# abstract class | + | Interfaces are declared in the same way as in C#, the only difference being that interfaces in D can have final methods, equivalent to a C# abstract class |
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | interface I | |
− | + | { | |
− | + | void Method(); | |
+ | } | ||
− | + | abstract class C | |
− | + | { | |
− | + | void Method() | |
− | + | { | |
− | + | Console.WriteLine("hello"); | |
− | + | } | |
+ | } | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | interface I | |
− | + | { | |
+ | void method(); | ||
+ | } | ||
− | + | interface C | |
− | + | { | |
− | + | final void method() | |
− | + | { | |
− | + | writeln("hello"); | |
− | + | } | |
+ | } | ||
+ | </syntaxhighlight> | ||
== Classes == | == Classes == | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
+ | class A { | ||
private int myValue; // not accessible from outside | private int myValue; // not accessible from outside | ||
Line 364: | Line 434: | ||
} | } | ||
− | + | } | |
− | + | class B: A { | |
public B(): base(3) {} | public B(): base(3) {} | ||
Line 373: | Line 443: | ||
return this.Value * 2; | return this.Value * 2; | ||
} | } | ||
− | + | } | |
− | + | [TestFixture] public class AboutClasses { | |
[Test]public void inheritance() { | [Test]public void inheritance() { | ||
B instance = new B(); | B instance = new B(); | ||
Assert.AreEqual(instance.GetDoubleValue(), 6); | Assert.AreEqual(instance.GetDoubleValue(), 6); | ||
} | } | ||
− | + | } | |
− | + | </syntaxhighlight> | |
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
+ | class A { | ||
private int myValue; // not accessible from outside | private int myValue; // not accessible from outside | ||
Line 390: | Line 461: | ||
myValue = startValue; | myValue = startValue; | ||
} | } | ||
− | + | } | |
− | + | class B: A { | |
this() { | this() { | ||
− | super(3); // | + | super(3); // call constructor on parent class A |
} | } | ||
auto getDoubleValue() { | auto getDoubleValue() { | ||
− | return myValue * 2; // B doesn't have this field but | + | return myValue * 2; // B doesn't have this field but can access the value |
} | } | ||
− | + | } | |
− | + | class AboutClasses { | |
@Test public void inheritance() { | @Test public void inheritance() { | ||
Line 408: | Line 479: | ||
assertEquals(instance.getDoubleValue(), 6); | assertEquals(instance.getDoubleValue(), 6); | ||
} | } | ||
− | + | } | |
+ | </syntaxhighlight> | ||
== Generic Types == | == Generic Types == | ||
Line 415: | Line 487: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | public class A { | |
public void Foo<T>(T arg) {} | public void Foo<T>(T arg) {} | ||
− | + | } | |
− | + | // use | |
− | + | A a = new A(); | |
− | + | a.Foo<int>(1); | |
− | + | public class C<T> where T: class { } | |
− | + | ... | |
− | + | C<A> c = new C<A>(); | |
− | + | // C<int> i = new C<int>(); compiler error: int isn't a class | |
− | + | public class X { } | |
− | + | public class D<T> where T: X {} | |
− | + | D<X> d = new D<X>(); | |
− | + | //D<A> d = new D<A>();compiler error: A isn't X | |
− | + | </syntaxhighlight> | |
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | void foo(T)(T arg) {} | |
− | + | // use | |
− | + | foo!int(3); | |
− | + | foo!string("bar"); | |
− | + | class X {} | |
− | + | class C(T) if is(T == class) {} | |
− | + | // use | |
− | + | C c = new C!X; | |
− | + | // C c = new C!int; compiler error: int isn't a class | |
− | + | class D(T) if is(T : X) {} | |
− | + | auto d = new D!X; | |
− | + | //auto d = new D!(A);compiler error: A isn't X | |
− | + | </syntaxhighlight> | |
=== Constraints === | === Constraints === | ||
There are no direct equivalents of other generic constraints, but the D template system is so versatile that you can create your own. | There are no direct equivalents of other generic constraints, but the D template system is so versatile that you can create your own. | ||
''The C# Way'' | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | class C<T> where T: new() {} | ||
+ | </syntaxhighlight> | ||
+ | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | class C(T) if (is(typeof(new T()) == T)) | ||
+ | </syntaxhighlight> | ||
+ | The template constraint will be read as if the result of expression <code>new T()</code> is of type T. If the T class has no constructor or is some other type, the <code>new T()</code> expression will result in an error or some other type, therefore the constraint will not be satisfied. | ||
+ | |||
+ | == Events == | ||
+ | |||
+ | There is no such concept in D language, but D programmers prefer to use [http://dlang.org/phobos/std_signals.html signals and slots] instead of events. | ||
+ | |||
+ | If you are keen to use events in D, a quick and dirty way to implement them can be found below (Credit: https://forum.dlang.org/post/dcdtuqyrxpteuaxmvwft@forum.dlang.org): | ||
+ | |||
+ | <syntaxhighlight lang="D"> | ||
+ | class Event(T...) | ||
+ | { | ||
+ | alias CB = void delegate(T); | ||
+ | CB[] callbacks; | ||
+ | |||
+ | // NOTE: This implementation uses the `+` operator as it is likely more | ||
+ | // familiar to C# programmers, but a more idiomatic D operator would be | ||
+ | // `~`, D's concatenation operator. | ||
+ | |||
+ | void opOpAssign(string op)(CB handler) | ||
+ | if (op == "+" || op == "-") | ||
+ | { | ||
+ | static if (op == "+") | ||
+ | callbacks ~= handler; | ||
+ | else | ||
+ | { | ||
+ | import std.algorithm.mutation : remove; | ||
+ | callbacks = callbacks.remove!(x => x == handler); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void opOpAssign(string op)(void function(T) handler) | ||
+ | if (op == "+" || op == "-") | ||
+ | { | ||
+ | import std.functional : toDelegate; | ||
+ | opOpAssign!op(toDelegate(handler)); | ||
+ | } | ||
+ | |||
+ | void opCall(T args) | ||
+ | { | ||
+ | foreach (cb; callbacks) | ||
+ | cb(args); | ||
+ | } | ||
+ | } | ||
− | + | class Property(T) | |
+ | { | ||
+ | private T _value; | ||
− | + | alias event = Event!(T, T); | |
− | + | auto changing = new event(); | |
+ | auto changed = new event(); | ||
− | + | T value() @property | |
+ | { | ||
+ | return _value; | ||
+ | } | ||
− | == | + | T value(T newValue) @property |
+ | { | ||
+ | if (_value != newValue) | ||
+ | { | ||
+ | T oldValue = _value; | ||
+ | changing(_value, newValue); | ||
+ | _value = newValue; | ||
+ | changed(oldValue, _value); | ||
+ | } | ||
+ | return _value; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void main() | ||
+ | { | ||
+ | auto test = new Property!(int)(); | ||
− | + | int changingCount; | |
+ | void onChanging(int oldValue, int newValue) | ||
+ | { | ||
+ | changingCount++; | ||
+ | } | ||
+ | test.changing += &onChanging; | ||
− | + | int changedCount; | |
+ | void onChanged(int oldValue, int newValue) | ||
+ | { | ||
+ | changedCount++; | ||
+ | } | ||
+ | test.changed += &onChanged; | ||
+ | |||
+ | test.value = 1; // value changed from 0 to 1 | ||
+ | test.value = 1; // value not changed | ||
+ | test.value = 2; // value changed from 1 to 2 | ||
− | + | assert(changingCount == 2); | |
+ | assert(changedCount == 2); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
== Dynamic Types == | == Dynamic Types == | ||
Line 492: | Line 652: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | [Test]public void boxing_test() { | |
int a = 1; | int a = 1; | ||
float b = 19.64f; | float b = 19.64f; | ||
Line 507: | Line 667: | ||
Assert.AreEqual(b, 19.64f); | Assert.AreEqual(b, 19.64f); | ||
Assert.AreEqual(tos(o), "19.64"); | Assert.AreEqual(tos(o), "19.64"); | ||
− | + | } | |
− | + | private string tos(object obj) { | |
return obj.ToString(); | return obj.ToString(); | ||
− | + | } | |
+ | </syntaxhighlight> | ||
+ | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | import std.variant; | ||
− | + | @Test void boxing_test() { | |
− | |||
int a = 1; | int a = 1; | ||
float b = 19.64f; | float b = 19.64f; | ||
Line 525: | Line 688: | ||
auto x = v.get!(float); | auto x = v.get!(float); | ||
assert(x == 19.64f); | assert(x == 19.64f); | ||
− | + | } | |
− | + | string tos(T)(T obj) { | |
return to!string(obj); | return to!string(obj); | ||
− | + | } | |
+ | </syntaxhighlight> | ||
== Nullable types == | == Nullable types == | ||
− | Value types are not nullable in D language. | + | Value types are not nullable in the D language. |
− | + | But there is a solution in the standard library module [[typecons]] - templated struct [[Nullable]] - to simulate this behavior. This is the same solution as in C#, although C# provides the shorthand syntax of "Type? variable-name", which can hide the usage of templated struct System.Nullable<>. | |
''[https://msdn.microsoft.com/en-us/library/vstudio/1t3y8s4s%28v=vs.110%29.aspx The C# Way]'' | ''[https://msdn.microsoft.com/en-us/library/vstudio/1t3y8s4s%28v=vs.110%29.aspx The C# Way]'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | int? a = null; // short hand for: System.Nullable<int> a = null; | |
− | System.Console.WriteLine( | + | // attempting to use a.Value will cause an exception to be thrown |
− | + | int? num = 10; | |
+ | if (num.HasValue) { | ||
+ | System.Console.WriteLine(3 * num.Value); // prints 30 | ||
+ | } else { | ||
System.Console.WriteLine("num = Null"); | System.Console.WriteLine("num = Null"); | ||
− | + | } | |
+ | </syntaxhighlight> | ||
''[http://dlang.org/phobos/std_typecons.html#.Nullable The D Way]'' | ''[http://dlang.org/phobos/std_typecons.html#.Nullable The D Way]'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | import std.typecons; | |
− | writeln(num.get()); | + | |
− | + | Nullable!int a; // a is null and cannot be used as an int until assigned a value | |
− | + | // attempting to use a as an int will cause an exception to be thrown | |
− | + | Nullable!int num = 10; | |
− | + | if ( ! num.isNull) { | |
+ | writeln(num.get * 3); // prints 30 | ||
+ | writeln(num * 3); // prints 30 - get not required (see below) | ||
+ | } else { | ||
+ | writeln("num = Null"); | ||
+ | } | ||
+ | int y = num; // get property not required due to "alias get this" inside Nullable for | ||
+ | // automatic compiler conversion to int when Nullable!(int) is not appropriate | ||
+ | int z = num.isNull ? int.init : num; | ||
+ | num.nullify(); // num set to null - cannot be used as an int until re-assigned | ||
+ | </syntaxhighlight> | ||
== Enumerable types == | == Enumerable types == | ||
Line 561: | Line 739: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
+ | class Prime: IEnumerable { | ||
object[] items = {1, 3, 5, 7}; | object[] items = {1, 3, 5, 7}; | ||
Line 571: | Line 750: | ||
} | } | ||
} | } | ||
− | + | } | |
− | + | [TestFixture] public class TestIterator { | |
[Test] public void prime_test() { | [Test] public void prime_test() { | ||
Prime primes = new Prime(); | Prime primes = new Prime(); | ||
Line 583: | Line 762: | ||
} | } | ||
} | } | ||
− | + | } | |
− | + | </syntaxhighlight> | |
''The D Way (front, popFront, empty)'' | ''The D Way (front, popFront, empty)'' | ||
− | + | <syntaxhighlight lang="D"> | |
− | + | class Prime { | |
private int[] p = [1, 3, 5, 7]; | private int[] p = [1, 3, 5, 7]; | ||
@property bool empty() { | @property bool empty() { | ||
Line 598: | Line 777: | ||
p = p[1..$]; | p = p[1..$]; | ||
} | } | ||
− | + | } | |
− | + | class TestIterator { | |
mixin UnitTest; | mixin UnitTest; | ||
Line 611: | Line 790: | ||
} | } | ||
} | } | ||
− | + | } | |
− | + | </syntaxhighlight> | |
''The D Way (opApply)'' | ''The D Way (opApply)'' | ||
− | ''' | + | '''TO DO''' |
== Code Attributes == | == Code Attributes == | ||
Line 621: | Line 800: | ||
''The C# Way'' | ''The C# Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | class Person | |
− | + | { | |
− | + | public Person() | |
− | public string Name { get; set; } | + | { |
+ | Name = "Default Name"; | ||
+ | } | ||
+ | |||
+ | public string Name { get; set; } | ||
} | } | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
− | + | <syntaxhighlight lang="C#"> | |
− | + | class Person | |
− | + | { | |
− | + | private string _name; | |
− | + | ||
+ | this() | ||
+ | { | ||
+ | _name = "Default Name"; | ||
+ | } | ||
− | + | @property string name() | |
− | + | { | |
− | + | return _name; | |
− | + | } | |
− | + | ||
− | + | @property void name(string n) | |
− | + | { | |
+ | _name = n; | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
= Statements = | = Statements = | ||
+ | |||
== Declaring variables == | == Declaring variables == | ||
''The C# Way'' | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | int x; | ||
+ | string text = "abc"; | ||
+ | const double PI = 3.14; | ||
+ | </syntaxhighlight> | ||
+ | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | int x; | ||
+ | string text = "abc"; | ||
+ | enum double PI = 3.14; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Expressions == | ||
+ | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | double average = (a + b) / 2; // assignment expression | ||
+ | foo.Bar(); // method call | ||
+ | |||
+ | SomeClass c = new SomeClass(); // class initialization | ||
+ | SomeStruct s = new SomeStruct(); //struct initialization | ||
+ | |||
+ | List<int> list = new List<int>(); | ||
+ | Dictionary<string, int> = new Dictionary<string, int>(); | ||
+ | </syntaxhighlight> | ||
+ | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | double average = (a + b) / 2; // assignment expression | ||
+ | foo.bar(); // method call | ||
+ | |||
+ | SomeClass c = new SomeClass(); // class initialization | ||
+ | SomeStruct s = SomeStruct(); //struct initialization | ||
+ | |||
+ | List!int list = new List!int(); | ||
+ | Dictionary!(string, int) = new Dictionary!(string, int); | ||
+ | </syntaxhighlight> | ||
+ | Differences: | ||
+ | * Structs in D are initialized without the <code>new</code> keyword. | ||
+ | * Generic types in D are initialized using <code>!</code> and by specifing types between parentheses. If there is only one specialization, parentheses can be omitted. | ||
+ | |||
+ | == Using namespaces == | ||
+ | |||
+ | D is importing modules instead of using namespaces. A collection of modules in D in named `package`. There is one-to-one correspondence between D modules and filenames and one-to-one correspondence between packages and folders. | ||
+ | |||
+ | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | using System; | ||
+ | using System.Collections; | ||
+ | </syntaxhighlight> | ||
+ | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | import system; | ||
+ | //this will try to import 'system.d' from the current folder. | ||
+ | //if 'system' is a folder name, it will try to import a special file named 'package.d' | ||
+ | import system.collections; | ||
+ | //this will import the file 'collections.d' from a folder named 'system'. | ||
+ | </syntaxhighlight> | ||
+ | if you are creating your own namespaces, the closest match for simulating C# namespaces is a trick used to import C++ code in D: | ||
− | int | + | ''The D Way'' |
− | + | <syntaxhighlight lang="D"> | |
− | + | extern (C++, MyNamespace) { | |
+ | class MyClass { ...} | ||
+ | } | ||
+ | //Fully qualified name of MyClass is MyNamespace.MyClass | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == if Statement == | ||
+ | The <code>if else</code> statement has the same structure as in C#: | ||
+ | |||
+ | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | bool b; | ||
+ | if (b) { ... } | ||
+ | |||
+ | int a; | ||
+ | if (a == 10) { .... } else { ... } | ||
+ | |||
+ | SomeClass c; | ||
+ | if (c == null) { ... } | ||
+ | void* p; | ||
+ | if (p != IntPtr.Zero) { ... } | ||
+ | |||
+ | string s = string.Empty; | ||
+ | if (string.IsNullOrEmpty(s)) { ... } | ||
+ | </syntaxhighlight> | ||
''The D Way'' | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | bool b; | ||
+ | if (b) { ... } | ||
+ | |||
+ | int a; | ||
+ | if (a == 10) { .... } else { ... } | ||
+ | if (a) { ... } | ||
+ | |||
+ | SomeClass c; | ||
+ | if (c is null) { ... } | ||
+ | if (!c) { ... } | ||
+ | |||
+ | void* p; | ||
+ | if (p) { ... } | ||
+ | |||
+ | string s = ""; | ||
+ | if(s.length == 0) { ... } | ||
+ | </syntaxhighlight> | ||
− | + | Differences: | |
− | |||
− | |||
+ | * null values are compared using is operator. The is operator has a very different meaning in D. | ||
+ | * In D, the conditional expression can be any other type than bool | ||
− | == | + | == switch Statement == |
− | ''' | + | The <code>switch</code> case statement has exactly the same structure as in C#, except that D provides a final switch statement intended to use with enum types. |
+ | |||
+ | ''The C# Way'' | ||
+ | <syntaxhighlight lang="C#"> | ||
+ | enum Color { Blue, Red, Green } | ||
+ | |||
+ | Color c; | ||
+ | switch (c) { | ||
+ | case Color.Blue: ... break; | ||
+ | case Color.Red: ... break; | ||
+ | default: ... break; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | ''The D Way'' | ||
+ | <syntaxhighlight lang="D"> | ||
+ | enum Color { Blue, Red, Green } | ||
+ | |||
+ | Color c; | ||
+ | final switch (c) { | ||
+ | case Color.Blue: ... break; | ||
+ | case Color.Red: ... break; | ||
+ | //compilation error if the switch statement does not treat all members of Color. | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | or [http://p0nce.github.io/d-idioms/#Qualified-switch-using-with using with] | ||
+ | |||
+ | ''The D Way'' | ||
− | = | + | <syntaxhighlight lang="D"> |
− | + | // The members of enums should be camelCased | |
+ | enum Color { blue, red, green } | ||
+ | |||
+ | Color c; | ||
+ | |||
+ | final switch() with (Color){ | ||
+ | case blue: ... break; | ||
+ | case red: ... break; | ||
+ | case green: ... break; | ||
+ | //compilation error if the switch statement does not treat all members of Color. | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | |||
− | |||
== Iteration statements == | == Iteration statements == | ||
'''TO DO''' | '''TO DO''' | ||
+ | |||
== Jump statements == | == Jump statements == | ||
'''TO DO''' | '''TO DO''' | ||
Line 677: | Line 1,005: | ||
= Argument Checking = | = Argument Checking = | ||
'''TO DO''' | '''TO DO''' | ||
+ | |||
+ | = nameof = | ||
+ | ''[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof The C# Way]'' | ||
+ | |||
+ | <syntaxhighlight lang="C#"> | ||
+ | using System; | ||
+ | using System.Diagnostics; | ||
+ | |||
+ | class Program | ||
+ | { | ||
+ | static void Main() | ||
+ | { | ||
+ | int myIdentifier; | ||
+ | Debug.Assert(nameof(myIdentifier) == "myIdentifier"); | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ''The D Way'' (Credit: https://forum.dlang.org/post/wcatxfktmqlgihzdmjtt@forum.dlang.org) | ||
+ | |||
+ | <syntaxhighlight lang="D"> | ||
+ | template nameOf(alias nameType) | ||
+ | { | ||
+ | enum string nameOf = __traits(identifier, nameType); | ||
+ | } | ||
+ | |||
+ | void main() | ||
+ | { | ||
+ | int myIdentifier; | ||
+ | assert(nameOf!myIdentifier == "myIdentifier"); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
= Resources = | = Resources = | ||
Line 682: | Line 1,042: | ||
* [https://github.com/rumbu13/sharp/blob/master/cstod.md Original article] | * [https://github.com/rumbu13/sharp/blob/master/cstod.md Original article] | ||
* [https://github.com/ilmanzo/DLangKoans/ D Koans] | * [https://github.com/ilmanzo/DLangKoans/ D Koans] | ||
− | + | * [https://github.com/taylorh140/Calling-NET-from-D Calling .NET from D] | |
[[Category:Languages versus D]] | [[Category:Languages versus D]] |
Latest revision as of 23:54, 9 May 2021
Contents
- 1 Introduction: Hello world
- 2 Documentation Comments
- 3 Compilation
- 4 Coding styles
- 5 Type system
- 6 Statements
- 7 Exception handling
- 8 Argument Checking
- 9 nameof
- 10 Resources
Introduction: Hello world
The C# Way
file: hello.cs
using System;
class Hello
{
static void Main(string[] args)
{
Console.WriteLine("Hello world!");
}
}
The D Way
file: hello.d
import std.stdio;
void main(string[] args)
{
writeln("Hello world!");
}
Things we have learnt so far:
- standard file extension for D source code files is
.d
- we
import
a module instead ofusing
a namespace; - methods (functions) can be top-level, that is, not declared inside a class; and called without being prefixed with an object/class or class name (e.g.
writeln
) writeln
is the D equivalent for C#'sSystem.Console.WriteLine
;- some syntax is exactly the same as in C# (method definitions, string qualifiers, array declarations, comments)
- many of the keywords are exactly the same (
void,string
);
Documentation Comments
The C# Way
/// <summary>
/// Documentation comment
/// </summary>
/// <param name="foo">...</param>
/// <param name="bar">...</param>
/// <returns>...</returns>
The D Way
/**
Documentation comment
Params: foo = param description
bar = param description
Returns: return value
Throws: Exception
*/
Compilation
Simple compilation
The C# Way
With mcs mono compiler:
$ mcs source1.cs source2.cs -out:program.exe
The D Way
With DMD Digital Mars compiler:
$ dmd source1.d source2.d -ofprogram
Conditional Compilation Directives
The C# Way
Defines one or more conditional symbols (short: -d)
-define:S1[;S2]
// compile with: /define:xx
// or uncomment the next line:
//#define xx
[Test] public void define_test() {
int x = 0;
#if (xx)
x = 5;
#else
x = 1;
#endif
Assert.AreEqual(x, 5);
}
The D Way
-version=S1
// compile with: -version=xx
// or uncomment the next line:
//version = xx
unittest {
int x = 0;
version(xx) {
x = 5;
}
else {
x = 1;
}
assert( x == 5 );
}
Coding styles
- D programmers prefer to use the camelCase notation instead of PascalCase for method names, variable names and enum members
- Module names (C# namespaces) are always in lowercase due to cross-platform compatibility regarding file names.
- If there are conflicts between a named entity and a keyword, in C# you can use verbatim identifiers (@while). D does not have verbatim identifiers, but the convention is to add an underscore at the end of the entity (while_).
Type | C# Way | D Way |
---|---|---|
Class | PascalCase | PascalCase |
Struct | PascalCase | PascalCase |
class method | PascalCase | camelCase |
class property | PascalCase | camelCase |
enum declaration | PascalCase | PascalCase |
enum value | PascalCase | camelCase |
const | UPPER_CASE | - |
local variable | camelCase | camelCase |
Type system
Built-in types
Basic type names are very similar in both languages with the following differences:
- The 8 bit signed integer from C#
sbyte
is written in D asbyte
; - The 8 bit unsigned integer from C#
byte
is written in D asubyte
; - There is no type equivalence for
decimal
- There are three types of char in D:
char
,wchar
anddchar
, each of them corresponding to an UTF encoding : UTF-8, UTF-16 and UTF-32. Since C# is using internally UTF-16 chars, the direct equivalent of C#char
is Dwchar
. - There are also three types of string in D:
string
,wstring
anddstring
. The direct equivalent of C#string
is in fact Dwstring
. These are not keywords in D, they are in fact declared as aliases to immutable char arrays. - There is another floating point type in D:
real
with no type equivalence in C#. - Complex floating point types
Complex<T>
are keywords in D:cfloat, cdouble, creal
and they imaginary counterparts areifloat
,idouble
,ireal
;
Literals
TO DO
Arrays
Arrays in D are not too different than the ones form C# (for D see D Koans and dlang)
Static array
Initialize with a literal
The C# Way
string[] fruits = {"banana", "mango", "apple", "orange"};
// or long mode
string[] longFruits = new string[4]{"banana", "mango", "apple", "orange"};
Assert.AreEqual(fruits[0], "banana");
Assert.AreEqual(fruits.Length, 4);
The D Way
string[4] fruits = ["banana", "mango", "apple", "orange"];
assertEquals(fruits[0], "banana");
assertEquals(fruits.length, 4);
Initialize with same value
The C# Way
// no short way
int[] b = { 1, 1, 1}; // 3 elements with same value 1
The D Way
int[3] b = 1; // 3 elements with same value 1
Dynamic array
The C# Way
List<T> behaves similarly to a dynamic array
using System.Collections.Generic;
...
List<string> fruits = new List<string>{"banana", "mango"};
Assert.AreEqual(fruits.Count, 2);
fruits.Add("strawberry");
Assert.AreEqual(fruits.Count, 3);
Assert.AreEqual(fruits[2], "strawberry");
The D Way
string[] fruits = ["banana", "mango"];
assertEquals(fruits.length, 2);
fruits ~= "strawberry";
assertEquals(fruits.length, 3);
assertEquals(fruits[2], "strawberry");
Pointers
Since D is not a managed language, you are free to use pointers anywhere in the code, without encompassing them in an unsafe context. On the contrary, D code is by default unsafe, but you can force the safe context using the @safe
keyword:
The C# Way
int value;
// here you can't use pointers
unsafe {
int* p = &value;
}
The D Way
int value;
int* p = &value;
@safe {
//here you can't use pointers
}
Delegates
Delegates in D are declared with the same keyword, but the return type precedes the declaration:
The C# Way
delegate int F
The D Way
int delegate(int x) Foo;
int function(int x) Foo;
Since D doesn't need to declare methods inside a class, you can declare also a function, equivalent to a delegate without class context. A notable difference between C# and D is the fact that delegates are not multicast in D, therefore you cannot join or remove them.
Enums
There is no difference between enum declarations, except that so called C# flags enums are not necessarely decorated with the [Flags] attribute:
The C# Way
enum Option
{
Option1,
Option2
}
[Flags]
enum Permission
{
read,
write,
all = read | wite
}
The D Way
The members of enums should be camelCased, so their first letter is lowercase. (see D Style
enum Color
{
red,
green,
blue
}
enum Permission
{
read,
write,
all = read | wite
}
Struct
Struct are declared exactly the same way in D except:
- There is no explicit layout in D. Nevertheless, there is a solution in the standard library.
- Structs cannot implement interfaces
The C# Way
public struct TimeOfDay {
public int hour;
public int minute;
public TimeOfDay(int h, int m) {
hour = h;
minute = m;
}
}
...
var t1 = new TimeOfDay(8, 30);
Assert.AreEqual(t1.minute, 30);
// Declare a struct object without "new"
TimeOfDay t2;
t2.minute = 10;
Assert.AreEqual(t2.minute, 10);
The D Way
struct TimeOfDay {
int hour;
int minute;
}
...
// preferred syntax
auto t1 = TimeOfDay(8, 30);
assert(t1.hour == 8);
assert(t1.minute == 30);
// alternate C syntax, deprecated
TimeOfDay t2 = {9, 45};
assert(t2.hour == 9);
// with designated initializers
auto t3 = {minute: 20, hour: 8};
assert(t3.hour == 8);
assert(t3.minute == 20);
auto t4 = TimeOfDay(10); // not all members need to be specified
assert(t4.hour == 10);
assert(t4.minute == 0);
Union
D has unions, the equivalent of a C# struct with explicit layout where all fields offsets are 0
The C# Way
[StructLayout(LayoutKind.Explicit)]
struct MyUnion {
[FieldOffset(0)]
int someInt;
[FieldOffset(0)]
float someFloat;
}
The D Way
union MyUnion {
int someInt;
float someFloat;
}
Interfaces
Interfaces are declared in the same way as in C#, the only difference being that interfaces in D can have final methods, equivalent to a C# abstract class
The C# Way
interface I
{
void Method();
}
abstract class C
{
void Method()
{
Console.WriteLine("hello");
}
}
The D Way
interface I
{
void method();
}
interface C
{
final void method()
{
writeln("hello");
}
}
Classes
The C# Way
class A {
private int myValue; // not accessible from outside
public A(int startValue) { // constructor
myValue = startValue;
}
public int Value {
get { return myValue; }
}
}
class B: A {
public B(): base(3) {}
public int GetDoubleValue() {
//return myValue * 2; // Error: myValue is inaccessible due to its protection level
return this.Value * 2;
}
}
[TestFixture] public class AboutClasses {
[Test]public void inheritance() {
B instance = new B();
Assert.AreEqual(instance.GetDoubleValue(), 6);
}
}
The D Way
class A {
private int myValue; // not accessible from outside
this(int startValue) { // constructor
myValue = startValue;
}
}
class B: A {
this() {
super(3); // call constructor on parent class A
}
auto getDoubleValue() {
return myValue * 2; // B doesn't have this field but can access the value
}
}
class AboutClasses {
@Test public void inheritance() {
auto instance = new B;
assertEquals(instance.getDoubleValue(), 6);
}
}
Generic Types
D is not using generics, instead it has a more powerful concept named templates.
The C# Way
public class A {
public void Foo<T>(T arg) {}
}
// use
A a = new A();
a.Foo<int>(1);
public class C<T> where T: class { }
...
C<A> c = new C<A>();
// C<int> i = new C<int>(); compiler error: int isn't a class
public class X { }
public class D<T> where T: X {}
D<X> d = new D<X>();
//D<A> d = new D<A>();compiler error: A isn't X
The D Way
void foo(T)(T arg) {}
// use
foo!int(3);
foo!string("bar");
class X {}
class C(T) if is(T == class) {}
// use
C c = new C!X;
// C c = new C!int; compiler error: int isn't a class
class D(T) if is(T : X) {}
auto d = new D!X;
//auto d = new D!(A);compiler error: A isn't X
Constraints
There are no direct equivalents of other generic constraints, but the D template system is so versatile that you can create your own.
The C# Way
class C<T> where T: new() {}
The D Way
class C(T) if (is(typeof(new T()) == T))
The template constraint will be read as if the result of expression new T()
is of type T. If the T class has no constructor or is some other type, the new T()
expression will result in an error or some other type, therefore the constraint will not be satisfied.
Events
There is no such concept in D language, but D programmers prefer to use signals and slots instead of events.
If you are keen to use events in D, a quick and dirty way to implement them can be found below (Credit: https://forum.dlang.org/post/dcdtuqyrxpteuaxmvwft@forum.dlang.org):
class Event(T...)
{
alias CB = void delegate(T);
CB[] callbacks;
// NOTE: This implementation uses the `+` operator as it is likely more
// familiar to C# programmers, but a more idiomatic D operator would be
// `~`, D's concatenation operator.
void opOpAssign(string op)(CB handler)
if (op == "+" || op == "-")
{
static if (op == "+")
callbacks ~= handler;
else
{
import std.algorithm.mutation : remove;
callbacks = callbacks.remove!(x => x == handler);
}
}
void opOpAssign(string op)(void function(T) handler)
if (op == "+" || op == "-")
{
import std.functional : toDelegate;
opOpAssign!op(toDelegate(handler));
}
void opCall(T args)
{
foreach (cb; callbacks)
cb(args);
}
}
class Property(T)
{
private T _value;
alias event = Event!(T, T);
auto changing = new event();
auto changed = new event();
T value() @property
{
return _value;
}
T value(T newValue) @property
{
if (_value != newValue)
{
T oldValue = _value;
changing(_value, newValue);
_value = newValue;
changed(oldValue, _value);
}
return _value;
}
}
void main()
{
auto test = new Property!(int)();
int changingCount;
void onChanging(int oldValue, int newValue)
{
changingCount++;
}
test.changing += &onChanging;
int changedCount;
void onChanged(int oldValue, int newValue)
{
changedCount++;
}
test.changed += &onChanged;
test.value = 1; // value changed from 0 to 1
test.value = 1; // value not changed
test.value = 2; // value changed from 1 to 2
assert(changingCount == 2);
assert(changedCount == 2);
}
Dynamic Types
There is no such concept in D language. All types must be known at compile time but the same semantics can be simulated by forwarding.
Boxing
Boxing, otherwise known as wrapping, is the process of placing a primitive type within an object so that the primitive can be used as a reference object (wikipedia)
Value types in D are not boxed or unboxed automatically and do not inherit from ValueType. Also, you cannot implement interfaces for value types. Depending on your purpose, there are alternatives to boxing in D:
- if you just want to have a container for several types you can use std.variant
- if you want to write general purpose method with object parameters, use generics
The C# Way
[Test]public void boxing_test() {
int a = 1;
float b = 19.64f;
object o = a;
Assert.AreEqual(o, 1);
Assert.AreEqual(tos(o), "1");
o = b;
Assert.AreEqual(o, 19.64f);
b = (float)o;
Assert.AreEqual(b, 19.64f);
Assert.AreEqual(tos(o), "19.64");
}
private string tos(object obj) {
return obj.ToString();
}
The D Way
import std.variant;
@Test void boxing_test() {
int a = 1;
float b = 19.64f;
Variant o = a;
assert(o.type == typeid(int));
assert(tos(o) == "1");
v = b;
auto x = v.get!(float);
assert(x == 19.64f);
}
string tos(T)(T obj) {
return to!string(obj);
}
Nullable types
Value types are not nullable in the D language. But there is a solution in the standard library module typecons - templated struct Nullable - to simulate this behavior. This is the same solution as in C#, although C# provides the shorthand syntax of "Type? variable-name", which can hide the usage of templated struct System.Nullable<>.
int? a = null; // short hand for: System.Nullable<int> a = null;
// attempting to use a.Value will cause an exception to be thrown
int? num = 10;
if (num.HasValue) {
System.Console.WriteLine(3 * num.Value); // prints 30
} else {
System.Console.WriteLine("num = Null");
}
import std.typecons;
Nullable!int a; // a is null and cannot be used as an int until assigned a value
// attempting to use a as an int will cause an exception to be thrown
Nullable!int num = 10;
if ( ! num.isNull) {
writeln(num.get * 3); // prints 30
writeln(num * 3); // prints 30 - get not required (see below)
} else {
writeln("num = Null");
}
int y = num; // get property not required due to "alias get this" inside Nullable for
// automatic compiler conversion to int when Nullable!(int) is not appropriate
int z = num.isNull ? int.init : num;
num.nullify(); // num set to null - cannot be used as an int until re-assigned
Enumerable types
In C#, a type can be considered enumerable if implements the IEnumerable interface. In D, a type is considered enumerable (Range is more used as the concept in D) if:
- implements three methods named popFront, empty and front or,
- implements a special operator named opApply;
The C# Way
class Prime: IEnumerable {
object[] items = {1, 3, 5, 7};
public IEnumerator GetEnumerator() {
foreach (object o in items) {
// Return the current element and then on next function call
// resume from next element rather than starting all over again;
yield return o;
}
}
}
[TestFixture] public class TestIterator {
[Test] public void prime_test() {
Prime primes = new Prime();
int[] expected = {1, 3, 5, 7};
int i = 0;
foreach (object p in primes) {
Assert.AreEqual(p, expected[i++]);
}
}
}
The D Way (front, popFront, empty)
class Prime {
private int[] p = [1, 3, 5, 7];
@property bool empty() {
return p.length == 0;
}
@property int front() {
return p[0];
}
void popFront() {
p = p[1..$];
}
}
class TestIterator {
mixin UnitTest;
@Test prime() {
Prime primes = new Prime();
int[] expected = [1, 3, 5, 7];
int i;
for (; !primes.empty; primes.popFront()) {
assert(primes.front == expected[i++]);
}
}
}
The D Way (opApply)
TO DO
Code Attributes
Property
The C# Way
class Person
{
public Person()
{
Name = "Default Name";
}
public string Name { get; set; }
}
The D Way
class Person
{
private string _name;
this()
{
_name = "Default Name";
}
@property string name()
{
return _name;
}
@property void name(string n)
{
_name = n;
}
}
Statements
Declaring variables
The C# Way
int x;
string text = "abc";
const double PI = 3.14;
The D Way
int x;
string text = "abc";
enum double PI = 3.14;
Expressions
The C# Way
double average = (a + b) / 2; // assignment expression
foo.Bar(); // method call
SomeClass c = new SomeClass(); // class initialization
SomeStruct s = new SomeStruct(); //struct initialization
List<int> list = new List<int>();
Dictionary<string, int> = new Dictionary<string, int>();
The D Way
double average = (a + b) / 2; // assignment expression
foo.bar(); // method call
SomeClass c = new SomeClass(); // class initialization
SomeStruct s = SomeStruct(); //struct initialization
List!int list = new List!int();
Dictionary!(string, int) = new Dictionary!(string, int);
Differences:
- Structs in D are initialized without the
new
keyword. - Generic types in D are initialized using
!
and by specifing types between parentheses. If there is only one specialization, parentheses can be omitted.
Using namespaces
D is importing modules instead of using namespaces. A collection of modules in D in named `package`. There is one-to-one correspondence between D modules and filenames and one-to-one correspondence between packages and folders.
The C# Way
using System;
using System.Collections;
The D Way
import system;
//this will try to import 'system.d' from the current folder.
//if 'system' is a folder name, it will try to import a special file named 'package.d'
import system.collections;
//this will import the file 'collections.d' from a folder named 'system'.
if you are creating your own namespaces, the closest match for simulating C# namespaces is a trick used to import C++ code in D:
The D Way
extern (C++, MyNamespace) {
class MyClass { ...}
}
//Fully qualified name of MyClass is MyNamespace.MyClass
if Statement
The if else
statement has the same structure as in C#:
The C# Way
bool b;
if (b) { ... }
int a;
if (a == 10) { .... } else { ... }
SomeClass c;
if (c == null) { ... }
void* p;
if (p != IntPtr.Zero) { ... }
string s = string.Empty;
if (string.IsNullOrEmpty(s)) { ... }
The D Way
bool b;
if (b) { ... }
int a;
if (a == 10) { .... } else { ... }
if (a) { ... }
SomeClass c;
if (c is null) { ... }
if (!c) { ... }
void* p;
if (p) { ... }
string s = "";
if(s.length == 0) { ... }
Differences:
- null values are compared using is operator. The is operator has a very different meaning in D.
- In D, the conditional expression can be any other type than bool
switch Statement
The switch
case statement has exactly the same structure as in C#, except that D provides a final switch statement intended to use with enum types.
The C# Way
enum Color { Blue, Red, Green }
Color c;
switch (c) {
case Color.Blue: ... break;
case Color.Red: ... break;
default: ... break;
}
The D Way
enum Color { Blue, Red, Green }
Color c;
final switch (c) {
case Color.Blue: ... break;
case Color.Red: ... break;
//compilation error if the switch statement does not treat all members of Color.
}
or using with
The D Way
// The members of enums should be camelCased
enum Color { blue, red, green }
Color c;
final switch() with (Color){
case blue: ... break;
case red: ... break;
case green: ... break;
//compilation error if the switch statement does not treat all members of Color.
}
Iteration statements
TO DO
Jump statements
TO DO
Exception handling
TO DO
Argument Checking
TO DO
nameof
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
int myIdentifier;
Debug.Assert(nameof(myIdentifier) == "myIdentifier");
}
}
The D Way (Credit: https://forum.dlang.org/post/wcatxfktmqlgihzdmjtt@forum.dlang.org)
template nameOf(alias nameType)
{
enum string nameOf = __traits(identifier, nameType);
}
void main()
{
int myIdentifier;
assert(nameOf!myIdentifier == "myIdentifier");
}