Difference between revisions of "Programming in D for CSharp Programmers"
(→nameOf) |
(→nameof) |
||
Line 1,009: | Line 1,009: | ||
'''TO DO''' | '''TO DO''' | ||
− | = | + | = nameof = |
''[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof The C# Way]'' | ''[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/nameof The C# Way]'' | ||
Revision as of 02:12, 20 January 2019
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> works very 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); // what happens here ?
}
auto getDoubleValue() {
return myValue * 2; // B doesn't have this field but..
}
}
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 contructor 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)
{
T _value;
alias event = Event!(T, T);
auto _changing = new event();
public event changing() @property
{
return _changing;
}
auto _changed = new event();
public event changed() @property
{
return _changed;
}
public T value() @property
{
return _value;
}
public 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");
}