Arbeiten mit .NET: Grundlagen: Datentypen/ Wertetypen und Verweistypen

Aus Wikibooks

Dieses Kapitel enthält Abschnitte für mehrere Varianten, die teilweise geprüft oder ergänzt werden müssen.
Problemstellen: Code für C++
Wenn du einen Teil geprüft oder geändert hast, kannst du den Hinweis darauf aus der Liste der Teile entfernen. Wenn der letzte Teil geprüft worden ist, kann die gesamte {{Vorlage:Fehlender Teil}} entfernt werden.

In diesem Kapitel wird besprochen, wie Variable und ihre Inhalte im Arbeitsspeicher behandelt werden und wie Änderungen vorgenommen werden.

Einführung[Bearbeiten]

.NET unterscheidet die Verarbeitung der Datentypen im Arbeitsspeicher:

  • Ein Typ ist dann ein Wertetyp, wenn die Stelle im Arbeitsspeicher, die für die Variable vorgesehen ist, auch den Wert enthält.
  • Andernfalls ist ein Typ ein Referenztyp (Verweistyp): Die Stelle im Arbeitsspeicher, die für die Variable vorgesehen ist, enthält nicht den eigentlichen Inhalt/Wert, sondern "nur" einen Verweis auf die Stelle im Arbeitsspeicher, wo sich die tatsächlichen Daten befinden.

Wertetypen werden direkt auf dem Stack (Stapelspeicher) abgelegt, Referenztypen (Verweistypen) dagegen auf dem Heap (dynamischen Speicher).

Wertetypen[Bearbeiten]

Für Variablen eines Wertetyps gelten folgende Regeln:

  • Die Variable liegt auf dem Stack (Stapelspeicher), und zwar mit ihrem Wert. Mit der Variablen wird direkt der Wert angesprochen.
  • Wenn die Variable an eine andere Variable übergeben wird (durch Zuweisung oder als Parameter für eine Methode), dann wird der Wert kopiert. Auf dem Stack gibt es dann eine weitere Variable mit dem gleichen Wert.
  • Ein Wertetyp kann nicht den Wert nullNothing in VB – annehmen. Es gibt aber ein Verfahren, mit dem auch einem Wertetyp der Wert null/Nothing zugewiesen werden kann.
  • Für Wertetypen gibt es immer einen Standardwert, der durch einen Standardkonstruktor festgelegt wird.
  • Alle Wertetypen sind abgeleitet von der Klasse System.ValueType. (Das ist jetzt nur eine Information, die hierhergehört; Sie brauchen sich darüber noch keine Gedanken zu machen.)

Standardwerte[Bearbeiten]

Je nach Programmiersprache wird der Standardwert automatisch zugewiesen; oder die Variable kann erst dann verwendet werden, wenn ausdrücklich ein Wert festgelegt wird. Der folgende Abschnitt (Teil der Main-Methode) ist in VB zulässig, aber in C# nicht:

Variable mit Wert versehen, bevor sie benutzt werden kann
C#-Quelltext
int i;
Console.WriteLine(i);
// Fehler CS0165: Verwendung der nicht zugewiesenen lokalen Variablen i
i = 10 * 20 + 30;
Console.WriteLine(i);
VB.NET-Quelltext
Dim i As Integer
Console.WriteLine(i)
' Ausgabe: Standardwert 0 für eine Integer-Variable
i = 10 * 20 + 30
Console.WriteLine(i)

Die folgenden Typen gehören zu den Wertetypen; fett gedruckt sind die einfachen Datentypen aus dem Überblick:

  • Strukturen, nämlich
    • Numerische Typen: Ganzzahlige Typen (auch Char), Gleitkommatypen (auch Decimal)
    • Boolean, DateTime
    • Weitere, auch benutzerdefinierte Strukturen
  • Enumerationen

Die benutzerdefinierten Strukturen sind immer Wertetypen – auch dann, wenn ihre Bestandteile ("Member") Verweistypen sind.

Deklarieren und Initialisieren[Bearbeiten]

Es gibt mehrere Möglichkeiten, Variablen eines Wertetyps zu deklarieren und zu initialisieren.

  • Die Variable wird mit ihrem Typ und einem Anfangswert festgelegt.
C++-Quelltext
int i = 7;
C#-Quelltext
int i = 7;
VB.NET-Quelltext
Dim i As Integer = 7
  • Die Variable wird mit ihrem Typ festgelegt; der Anfangswert wird später zugewiesen.
C++-Quelltext
char c;
c = 'a';
C#-Quelltext
char c;
c = 'a';
VB.NET-Quelltext
Dim c As Char
c = "a"c
  • Die Variable wird mit ihrem Typ und dem Standardwert festgelegt.
C++-Quelltext
Point* p = new Point();
C#-Quelltext
Point p = new Point();
VB.NET-Quelltext
Dim p As new Point
  • Die Variable wird mit einem Anfangswert festgelegt; der Compiler erkennt daraus selbständig einen Typ.
C++-Quelltext
auto d = 7;      // ab C++0x (Dieser Standard wurde noch nicht verabschiedet!)
C#-Quelltext
var d = 7d;      // zulässig ab C# 3.0, das angehängte d legt einen double-Wert fest
VB.NET-Quelltext
Dim d = 7r       // das angehängte r legt einen double-Wert fest

Vor allem für Strukturen, die keine einfachen Datentypen sind, wird in der Regel ein spezieller Konstruktor mit Parametern verwendet. Das folgende Beispiel ist geeignet, um die Position eines Formulars auf dem Bildschirm festzulegen.

C++-Quelltext
Point location(100, 50);
C#-Quelltext
Point location = new Point(100, 50);
VB.NET-Quelltext
Dim location As New Point(100, 50)


Typ zuordnen ohne genaue Festlegung[Bearbeiten]

Im Kapitel Überblick über Datentypen hatten wir betont, dass in .NET zu jeder Variable ihr Datentyp genau festgelegt werden muss, aber im vorletzten Beispiel hatten wir darauf verzichtet. Wie ist dieser scheinbare Widerspruch aufzulösen?

In Wahrheit ist es kein Widerspruch. Bereits der Compiler sucht aus dem zugewiesenen Wert einen passenden Typ heraus; dieser Typ wird in der Anwendung als Datentyp der Variable verwendet und ist damit eindeutig festgelegt. Wenn der Wert 7 (aus dem Beispiel) ohne angehängten Buchstaben ("Postfix") angegeben wäre, würde der Compiler dies als Int32-Wert interpretieren; durch das o.g. Postfix ist klar, dass es als Double-Wert zu verstehen ist.

Diese automatische Erkennung und Verwendung des Datentyps ist nur möglich, wenn ein Anfangswert direkt zugewiesen wird. Dieses Verfahren kann auch nur innerhalb von Programmblöcken einschließlich Methoden verwendet werden, nicht aber für Elemente einer Klasse.

Werte zur Laufzeit ändern[Bearbeiten]

Die obigen Regeln – Variable gleich Wert, Übergabe durch Kopie – wirken sich in der Praxis beispielsweise so aus. In der folgenden Anwendung wird die Variable i1 deklariert, initialisiert und durch Rechnung verändert. Danach wird sie an eine andere Methode übergeben, dort ebenfalls verändert. Anschließend wird sie in der Main-Methode nochmals bearbeitet. Jeder einzelne Zustand wird durch Console.WriteLine dokumentiert.

C#-Quelltext
using System;

namespace Wikibooks.CSharp.ConsoleApp
{
	class Program
	{
		static void Main(string[] args)
		{
			int i1 = 7, i2 = 11;
			i1 = i1 * i2;
			Console.WriteLine( "Main:   " + i1.ToString());
			Change(i1);
			Console.WriteLine( "Main:   " + i1.ToString());
			i1 = i1 - 29;
			Console.WriteLine( "Main:   " + i1.ToString());
			Console.WriteLine( "Ende mit beliebiger Taste" );
			Console.ReadKey();
		}
		
		static void Change(int i1)
		{
			i1 = i1 * 2 + 13;
			Console.WriteLine( "Change: " + i1.ToString());			
		}		
	}	
}
VB.NET-Quelltext
Namespace Wikibooks.VBNet.ConsoleApp
	Module Program
		Sub Main()
			Dim i1 As Integer = 7 
			Dim i2 As Integer = 11 
			i1 = i1 * i2
			Console.WriteLine( "Main:   " + i1.ToString())
			Change(i1)
			Console.WriteLine( "Main:   " + i1.ToString())
			i1 = i1 - 29
			Console.WriteLine( "Main:   " + i1.ToString())
			Console.WriteLine( "Ende mit beliebiger Taste" )
			Console.ReadKey()
		End Sub
		Sub Change(i1 As Integer)
			i1 = i1 * 2 + 13
			Console.WriteLine( "Change: " + i1.ToString())
		End Sub
	End Module
End Namespace
Ausgabe
Main:   77
Change: 167
Main:   77
Main:   48
Ende mit beliebiger Taste

Sie sehen: Die Änderungen, die mit i1 innerhalb von Change vorgenommen wurden, haben in Main keine Auswirkungen. Es handelt sich bei dem i1 von Change tatsächlich um eine Kopie, nicht um das Original.

Andererseits gibt es durchaus Situationen, in denen Änderungen aus der aktuellen Methode ausgelagert wurden, aber für die weitere Verarbeitung benötigt werden. Dies wird bei den Rückgabewerten von Methoden/Funktionen behandelt.

Referenztypen[Bearbeiten]

Für Variablen eines Referenztyps gelten folgende Regeln:

  • Die Variable liegt auf dem Heap (dynamischen Speicher), und zwar mit einem Verweis auf den eigentlichen Ort im Arbeitsspeicher. Mit der Variablen wird nicht der Wert, sondern der Verweis angesprochen.
  • Wenn die Variable an eine andere Variable übergeben wird (durch Zuweisung oder als Parameter für eine Methode), dann wird der Verweis kopiert. Auf dem Heap gibt es eine weitere Variable mit dem gleichen Verweis. Im Arbeitsspeicher selbst gibt es den Inhalt bzw. Wert unverändert nur einmal; er kann aber mit zwei verschiedenen Variablen benutzt werden.
  • Ein Referenztyp kann den Wert null/Nothing annehmen.
  • null/Nothing ist auch der Standardwert, solange keine Instanz erzeugt worden ist.

Hinweis für C++-Programmierer: Ein Referenztyp ist kein Zeiger, auch wenn die Formulierungen ähnlich lauten. Sie können ihn sich als Pointer vorstellen, der nicht dereferenziert werden muss, sondern mit dem direkt das Objekt zur Verfügung steht.

Die folgenden Typen gehören zu den Referenztypen; fett gedruckt sind die einfachen Datentypen aus dem Überblick:

  • Instanzen der Klasse System.String
  • Instanzen der Klasse System.Object
  • Alle Arrays, auch wenn ihre Elemente Wertetypen sind
  • alle von System.Object abgeleiteten Klassen, beispielsweise Formulare
  • Delegaten

Deklarieren und verwenden[Bearbeiten]

Eine Variable eines Referenztyps muss geeignet deklariert werden; für die Verwendung ist immer ein Konstruktor aufzurufen – entweder der Standardkonstruktor (ohne Parameter) oder ein spezieller Konstruktor (mit Parametern):

C++-Quelltext
// mit dem Standardkonstruktor
LoginForm* login = NULL;              // NULL muss angegeben werden, sonst undefiniert; 
login = new LoginForm();
// mit einem speziellen Konstruktor
Font* fat = new Font("Arial", 14, FontStyle.Bold);
C#-Quelltext
// mit dem Standardkonstruktor
LoginForm login;              // Standardwert null
login = new LoginForm();
// mit einem speziellen Konstruktor
Font fat = new Font("Arial", 14, FontStyle.Bold);
VB.NET-Quelltext
// mit dem Standardkonstruktor
Dim login As LoginForm        // Standardwert Nothing
login = New LoginForm()
// mit einem speziellen Konstruktor
Dim fat As Font = new Font("Arial", 14, FontStyle.Bold)

Die Variable selbst kann sofort nach der Deklaration verwendet werden, weil ihr immer der Standardwert null/Nothing zugewiesen wird, Ausnahme C++ hier muss man dem Zeiger NULL übergeben sonst zeigt der Zeiger irgendwohin im Speicher nur nicht dort hin wo es sein soll. NULL wird in C++ nie standardmäßig zugewiesen. Das Objekt, das über die Variable benutzt werden soll, steht (natürlich) erst dann zur Verfügung, wenn es erzeugt wurde und der Variablen zugewiesen wurde. Andernfalls gibt es eine NullReferenceException.

Es gibt noch weitere Verfahren, durch die man eine Referenz auf ein Objekt erhält. Solche Wege gibt es vor allem in den "Themen"-Büchern immer wieder. Dabei wird eine bestimmte Methode aufgerufen, die als Ergebnis ein neues Objekt liefert und der Referenz-Variablen zuweist. Ein einfaches Beispiel ist die Split-Methode der String-Klasse.

Aufgabe
Aufgabe

Gegeben ist eine Zeile einer csv-Datei, getrennt durch Semikolon. Gesucht ist die Liste aller Bestandteile.

C#-Quelltext
string input = "Herr;Jürgen;Thomas;Postfach 72 63 44;13156;Berlin";
string[] values = input.Split(';');
Console.WriteLine(values.Length);
VB.NET-Quelltext
Dim input As String = "Herr;Jürgen;Thomas;Postfach 72 63 44;13156;Berlin"
Dim values() As String = input.Split(";"c)
Console.WriteLine(values.Length)

Mit Split wird der gegebene String aufgeteilt; die einzelnen Stücke werden in einem neuen String-Array aufgelistet. Dieses Array befindet sich "irgendwo" im Arbeitsspeicher; dessen Position wird in einer Referenz registriert und an das Programm übergeben, nämlich an die Variable values. Diese Variable ist also die (neue) Referenz auf ein neues Array.

Die folgende Arbeitsweise, die Anfängern gerne unterläuft, ist eher unsinnig. Nehmen wir an, wir haben im Arbeitsspeicher ein DataSet namens myDataSet, das bereits mit mehreren Tabellen (DataTable) gefüllt ist. Auf eine bestimmte DataTable wollen wir mit einer eigenen Variablen table direkt zugreifen und legen dies so fest:

C#-Quelltext
DataTable table = new DataTable();
table = myDataSet.Tables[2];
VB.NET-Quelltext
Dim table As DataTable = New DataTable()
table = myDataSet.Tables(2)

Bei diesem Vorgehen deklarieren wir die gewünschte Variable. Gleichzeitig erzeugen wir eine neue DataTable ("nackt" ohne Inhalt, also ohne Zeilen und Spalten festzulegen) im Arbeitsspeicher und weisen diese Tabelle der Variablen zu. Im nächsten Schritt holen wir uns aus myDataSet die eigentlich gewünschte DataTable und weisen diese der Variablen dazu. Dadurch hängt die "nackte" DataTable sinnlos im Arbeitsspeicher herum und wird nie wieder benutzt.

Sinnvoll sind die folgenden Verfahren:

C#-Quelltext
// Variante 1 ohne ausdrückliche Initialisierung (gleichwertig zu 2)
DataTable table;
table = myDataSet.Tables[2];
// Variante 2 mit Initialisierung auf null 
DataTable table = null;
table = myDataSet.Tables[2];
// Variante 3 mit direkter Zuweisung
DataTable table = myDataSet.Tables[2];
VB.NET-Quelltext
' Variante 1 ohne ausdrückliche Initialisierung (gleichwertig zu 2)
Dim table As DataTable
table = myDataSet.Tables(2)
' Variante 2 mit Initialisierung auf Nothing
Dim table As DataTable = Nothing
table = myDataSet.Tables(2)
' Variante 3 mit direkter Zuweisung
Dim table As DataTable = myDataSet.Tables(2)

Merke: Erzeuge ein neues Objekt mit einem Standardkonstruktor nur dann, wenn dieses Objekt wirklich benötigt wird. Mehr Informatinen und Beispiele gibt es überall dort, wo Klassen, Objekte und Konstruktoren behandelt werden.

Werte zur Laufzeit ändern[Bearbeiten]

Die obigen Regeln – Variable ist Verweis, kopiert wird der Verweis – wirken sich in der Praxis beispielsweise so aus. In der folgenden Anwendung wird die Variable info als Information zu einer Datei, nämlich als FileInfo-Instanz deklariert und erzeugt; es werden Datum/Zeit der letzten Änderung ausgelesen. Danach wird sie an eine andere Methode übergeben; dort werden Datum/Zeit geändert. Jeder einzelne Zustand wird durch Console.WriteLine dokumentiert.

Als Datei für diesen Versuch sollten Sie eine völlig unwichtige Datei aus einem Temp-Verzeichnis benutzen.

C#-Quelltext
using System;
using System.IO;

namespace Wikibooks.CSharp.ConsoleApp
{
	class Program
	{
		const string name = @"E:\Temp\B09E159P.pdf";
		
		static void Main(string[] args)
		{
			FileInfo info = new FileInfo(name);
			Console.WriteLine( "Main:   " + info.LastWriteTime.ToString());
			Change(info);
			Console.WriteLine( "Main:   " + info.LastWriteTime.ToString());
			Console.ReadKey();
		}
		
		static void Change(FileInfo info)
		{
			Console.WriteLine( "Change: " + info.LastWriteTime.ToString());
			info.LastWriteTime = DateTime.Now.AddDays(-1);
			Console.WriteLine( "Change: " + info.LastWriteTime.ToString());
		}
	}
}
VB.NET-Quelltext
Imports System.IO

Namespace Wikibooks.VBNet.ConsoleApp
	Module Program
		Const name As String = "E:\Temp\B09E159P.pdf"
		Sub Main()
			Dim info As FileInfo = New FileInfo(name)
			Console.WriteLine( "Main:   " + info.LastWriteTime.ToString())
			Change(info)
			Console.WriteLine( "Main:   " + info.LastWriteTime.ToString())
			Console.ReadKey()
		End Sub
		Sub Change(info As FileInfo)
			Console.WriteLine( "Change: " + info.LastWriteTime.ToString())
			info.LastWriteTime = DateTime.Now.AddDays(-1)
			Console.WriteLine( "Change: " + info.LastWriteTime.ToString())
		End Sub
	End Module
End Namespace
Ausgabe
Main:   05.05.2009 09:25:14
Change: 05.05.2009 09:25:14
Change: 20.02.2010 13:13:07
Main:   20.02.2010 13:13:07

Sie sehen: Die Änderungen, die mit info innerhalb von Change vorgenommen wurden, stehen anschließend in Main ebenfalls zur Verfügung. Es handelt sich bei dem info von Change zwar um eine Kopie der Referenz, aber um dasselbe Objekt der Dateiinformation.

Anmerkungen[Bearbeiten]

Wozu Referenzen?[Bearbeiten]

Wozu gibt es eigentlich diese Unterschiede zwischen Verweis- und Wertetyp? Denken wir mal kurz nach:

Stellen wir uns vor, wir wären Bauleiter auf einer Baustelle, und heute früh ist eine große Ladung Steine gekommen. Mittags kommen die Maurer von einer anderen Baustelle und fragen, wo die Steine sind. Jetzt haben wir die Wahl:

  • Wir bringen die Steine zu den Maurern.
  • Wir zeigen den Maurern, wo die Steine sind.

Was wird wohl schneller und einfacher sein? Genauso ist es mit der Programmierung:

Wir haben eine riesige Datentabelle im Speicher. Jetzt wollen wir in einer anderen Methode diese Tabelle bearbeiten, indem wir eine Zeile dieser Tabelle löschen. Wäre die Datentabelle ein Wertetyp, müsste .NET die gesamte Tabelle in die andere Methode rüberschaufeln. Wenn wir aber nur einen Verweis auf die Stelle übergeben, an der sich die Tabelle im Speicher befindet, können wir sie in der anderen Methode ebenfalls bearbeiten… und unser Programm wird erheblich schneller, weil wir eben nicht mehr riesige Datenberge umschaufeln müssen.

Wozu unterscheiden?[Bearbeiten]

Andererseits sind unter .NET alles Objekte, auch die einfachen Datentypen. Warum werden diese dann als Wertetyp anders behandelt? Dies hat ganz einfach Effizienzgründe: Es ist viel sinnvoller und schneller in der Bearbeitung, wenn "einfache" Datentypen direkt im Zugriff stehen, auch wenn die theoretischen Grundlagen wegen der Unterscheidung etwas komplizierter werden; es wäre ungünstiger, wenn alles einheitlich über Referenzen behandelt würde, aber dafür der "einfache" Zugriff auf "einfache" Werte umständlicher würde.

Elemente, die keine Typen sind[Bearbeiten]

Die folgenden Bestandteile eines Programms sind keine Typen, weil keines dieser Elemente als Datentyp für ein bestimmtes Element genutzt werden kann:

  • Namespace
  • Quelldateien
  • Eigenschaften, Methoden, Ereignisse
  • Variable, Konstante, Felder

Hinweis für Spezialisten: Es ist möglich, auch alle diese Elemente bei einem laufenden Programm anzeigen zu lassen. Dafür müssen ebenfalls Datentypen verwendet werden, und zwar ganz spezielle wie Type oder MethodInfo. Sie können sicher sein, dass solche Informationen erst dann von Bedeutung sind, wenn Sie sich intensiv mit den "Innereien" von .NET befassen wollen, aber in absehbarer Zeit noch nicht.

Der Datentyp dynamic[Bearbeiten]

Die .NET 4.0 Laufzeit bringt eine neue Laufzeit mit sich, die DLR. Angelehnt an die dem Framework zugrunde liegende Common Language Runtime (CLR), ist dies die Dynamic Language Runtime. Die CLR fordert, wie bei allen streng typisierten Programmiersprachen, dass eine Variable bereits zur Übersetzungszeit eindeutig deklariert ist. (Dies gilt ja auch für var, denn zB var text = @"text"; legt ja text so fest, dass es eine Variable vom Typ System.String ist.) Die DLR hingegen erlaubt es, dass der Typ einer Variablen erst zur Laufzeit (also während der Ausführung des Programms) festgelegt und sogar später noch verändert werden kann. Erst durch die DLR sind Implementierungen von untypisierten Sprachen für das .NET Framework. Beispiele sind hier Python und Ruby.

In C# existiert der Typ dynamic, der all die Dinge erlaubt, die in untypisierten Sprachen wie JavaScript oder PHP möglich sind. Das Anwenden dieses Typs erfordert jedoch einige Erfahrung. Wird dynamic präzise und kontrolliert verwendet, ermöglicht dieser Typ, zuvor kaum verständlichen oder wenigstens schrecklich umständlichen Code auf das Wesentliche zu reduzieren, insbesondere dadurch, dass sich Sonderbehandlungen je nach angeliefertem Typ samt länglicher if-else if-else if... oder switch-case-case-case... Litaneien damit sparen lassen.

Anwendung vom Typ dynamic
C#-Quelltext
// Angenommen, wir haben die drei folgenden Klassen:
public class Address
{
    public string Street { get; set; }
    public int StreetNo { get; set; }
    public string SupplementalInfo { get; set; }
    public Guid CityId { get; set; }
    public Guid CountryId { get; set; }
}

public class Customer
{
    public Guid ContactPersonId { get; set; }
    public Address Address { get; set; }
}

public class Supplier
{
    public string SupplierName { get; set; }
    public Address Address { get; set; }
}

// ... und wir wollen wegen des Weihnachtsanschreibens an alle unsere
// Kunden und Lieferanten wissen, wie viele von ihnen in einem bestimmten
// Land wohnen, um die Portokosten zu optimieren, dann sähe das klassisch
// so aus:

public int CountCustomersAndSuppliersThatAreIn(Guid country, IEnumerable<Customer> customers, IEnumerable<Supplier> suppliers)
{
    var result = CountCustomers(country, customers);
    result += CountSuppliers(country, suppliers);

    return result;
}

int CountCustomers(Guid country, IEnumerable<Customers> customers)
{
    var count = 0;
    foreach (var c in customers)
        if (c.Address.CountryId == country)
            count++;

    return count;
}

int CountSuppliers(Guid country, IEnumerable<Suppliers> suppliers)
{
    var count = 0;
    foreach (var s in suppliers)
        if (s.Address.CountryId == country)
            count++;

    return count;
}

// Wie man sieht, sind die Methoden CountCustomers() und CountSuppliers()
// bis auf die Benamung identisch. Eigentlich it es doppelte Code-Haltung
// und widerspricht damit einem der grundlegensten Prinzipien, die für die
// Software-Entwicklung gilt. Mit "dynamic" sieht die Sache viel einfacher
// aus:

public int CountContactsThatAreIn(Guid country, IEnumerable<dynamic> contacts)
{
    var count = 0;

    foreach (var c in contacts)
        if (c.Address.CountryId == country)
            count++;

    return count;
}

// Es MUSS natürlich sichergestellt sein, dass in der contacts-Liste
// nur solche Objekte enthalten sind, die über die Eigenschaft "Address"
// verfügen (s.u.). Das war mit "präzise und kontrolliert" gemeint.
//
// Insbesondere können die "contacts" nun auch weitere Typen sein außer
// Kunden und Lieferanten, etwa Geschäftspartner, der Steuerberater usw.,
// sofern diese Typen die Eigenschaft "Address" haben -- ganz ohne auch
// nur eine Zeile Code in der Count...() Methode zu ändern.


Im Kommentar am Ende es obigen Stückchens Quellcode wurde gesagt, dass sichergestellt sein muss, dass auf Objekte so zugegriffen wird, dass die angenommenen Eigenschaften auch vorhanden sind. Ein fabelhafter Kandidat dazu ist eine Extension-Methode. Der Beispielcode zeigt, wie es geht.

Anwendung vom Typ dynamic
C#-Quelltext
public class Address
{
    public string Street { get; set; }
    public int StreetNo { get; set; }
    public string SupplementalInfo { get; set; }
    public Guid CityId { get; set; }
    public Guid CountryId { get; set; }
}

public class Customer
{
    public Guid ContactPersonId { get; set; }
    public Address Address { get; set; }
}

public class Supplier
{
    public string SupplierName { get; set; }
    public Address Address { get; set; }
}

public int CountContactsThatAreIn(Guid country, IEnumerable<dynamic> contacts)
{
    var count = 0;

    foreach (var c in contacts)
    {
        // Das ist neu:
        if (!(c.Implements(@"Address"))) continue;
        if (c.Address.CountryId == country)
            count++;
    }

    return count;
}

// Das ist die Extension (Extension-Klassen sind immer static und public):
static public ObjectExtenders
{
    // Die this type name-Syntax zeichnet eine Extension aus. Dem ersten Parameter
    // wird die Instanz übergeben, die erweitert werden soll.
    static public bool Implements(this object thing, string memberName)
    {
        if (thing == null) return false; // no instance given, thus not implemented.
        if (string.IsNullOrWhiteSpace(memberName)) return true; // no name given, thus implemented.
        return thing.GetType().GetMember(memberName).Length > 0;
    }
}


Übungen[Bearbeiten]

Übung 1 Wertetypen Zur Lösung

Welcher der folgenden Aussagen sind richtig, welche sind falsch?

  1. Bei einem Wertetyp wird zur Variablen direkt der Wert gespeichert.
  2. Wenn die Variable eines Wertetyps an eine zweite Variable übergeben wird, wird der Wert im Arbeitsspeicher kopiert.
  3. Wenn die Variable eines Wertetyps als Parameter an eine Methode übergeben wird, kann ihr Wert dort geändert werden, sodass die Änderung auch anschließend zur Verfügung steht.
  4. Der Standardwert eines Wertetyps lautet 0.
  5. Der Standardwert eines Wertetyps lautet null/Nothing.
  6. Die Deklaration kann u.a. dadurch erfolgen, dass die Variable mit ihrem Typ und dem Standardwert festgelegt wird.

Übung 2 Referenztypen Zur Lösung

Welcher der folgenden Aussagen sind richtig, welche sind falsch?

  1. Bei einem Referenztyp wird zur Variablen nicht der Inhalt gespeichert, sondern ein Verweis auf die Stelle im Arbeitsspeicher, wo der Inhalt steht.
  2. Wenn die Variable eines Referenztyps an eine zweite Variable übergeben wird, wird der Inhalt im Arbeitsspeicher kopiert.
  3. Wenn die Variable eines Referenztyps als Parameter an eine Methode übergeben wird, kann ihr Inhalt dort geändert werden, sodass die Änderung auch anschließend zur Verfügung steht.
  4. Der Standardwert eines Referenztyps lautet 0.
  5. Der Standardwert eines Referenztyps lautet null/Nothing.
  6. Die Deklaration kann u.a. dadurch erfolgen, dass die Variable mit ihrem Typ und dem Standardwert festgelegt wird.
Lösungen

Lösung zu Übung 1 Wertetypen Zur Übung

Die Aussagen 1, 2, 6 sind richtig. Die Aussagen 3, 4, 5 sind falsch.

Hinweis zu 4: Dazu haben wir bisher nichts gelesen. Bei Zahlen stimmt diese Aussage sogar; aber was soll eine 0 (Null) bei einem logischen Wert oder einer Struktur wie Point bedeuten? Allgemein ist sie also falsch.

Lösung zu Übung 2 Referenztypen Zur Übung

Die Aussagen 1, 3, 5, 6 sind richtig. Die Aussagen 2, 4 sind falsch.