Versiegelt

C# stellt das Sealed-Schlüsselwort bereit, um zu vermeiden, dass von der Klasse, welche mit dem Sealed-Schlüsselwort versehen wurde, zu erben. Versucht man von einer versiegelten Klasse zu erben, bekommt dies auch sofort mit einem Kompilier-Fehler quittiert. Das Sealed-Schlüsselwort kann ebenso auf Methoden angewendet werden. In diesem Falle kann zwar von der Klasse geerbt werden, aber eben nicht von der gekennzeichneten Methode.

Im Übrigen sind Structs von Hause versiegelt. Man kann also von diesen nicht erben. Mehr zum Unterschied zwischen Struct und Klasse gibt es hier. Fortsetzung folgt.

Aber wozu soll das Sealed-Schlüsselwort gut sein? Zum einen kann es eben eine Restriktion sein, die fachlich bzw. inhaltlich bedingt ist. Vielleicht erkennt der Entwickler oder Architekt einfach, dass es fachlich überhaupt keinen Sinn machen würde, von einer entsprechenden Klasse erben zu können und verhindert dies mit der „Versiegelung“. Auf der anderen Seite wird häufig auch ein Performance-Vorteil als Argument für die Verwendung des Schlüsselwortes angegeben. So ist dies auch in der „The C# Programming Language- Third Edition“ von Hejlsberg, Torgersen, Wilthamuth und Golde auf Seite 409 nachzulesen. Dort findet man folgendes Statement:

The sealed modifier is primarily used to prevent unintended derivation, but it also enables certain runtime optimizations. In particular, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed class instances into nonvirtual invocations.“

Grund genug, der Sache mal auf den Grund zu gehen. Dafür habe ich eine kleines Beispiel zusammengestellt und Geschwindigkeitstest durchgeführt.

Ich habe zwei Klassen, die beide die Schnittstelle IPerson implementieren.

public interface IPerson
    {
        string FirstName { get; }

        string LastName { get; }

        string GetFullName();
    }

Die erste Klasse Person wird nicht „versiegelt“.

public class Person : IPerson
    {
        public Person()
        {
            this.FirstName = "Max";
            this.LastName = "Mustermann";
        }

        public string FirstName { get; private set; }

        public string LastName { get; private set; }

        public string GetFullName()
        {
            return string.Format("{0} {1}", this.FirstName, this.LastName);
        }
    }

Die zweite wird mit dem Sealed-Schlüsselwort versehen und sieht wie folgt aus.

public sealed class SealedPerson : IPerson
    {
        public SealedPerson()
        {
            this.FirstName = "Max";
            this.LastName = "Mustermann";
        }

        public string FirstName { get; private set; }

        public string LastName { get; private set; }

        public string GetFullName()
        {
            return string.Format("{0} {1}", this.FirstName, this.LastName);
        }
    }

Folgender Test-Code wird dann über eine Console ausgeführt, um zu prüfen, ob es Unterschiede in der Ausführung zur Laufzeit gibt. Zuerst wird dabei die Ausführungsgeschwindigkeit beider Klassen bei Instanzierung nebst Methoden-Aufruf bei 1000 Wiederholungen geprüft, danach wird die Klasse einmal instanziert und danach nur noch die Geschwindigkeit von 1000 Methodenaufrufen gemessen.

class Program
    {
        static void Main(string[] args)
        {
            var stopwatch1 = new Stopwatch();
            stopwatch1.Start();

            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(new Person().GetFullName());
            }

            stopwatch1.Stop();

            var stopwatch2 = new Stopwatch();
            stopwatch2.Start();

            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(new SealedPerson().GetFullName());
            }

            stopwatch2.Stop();

            var person = new Person();
            var stopwatch3 = new Stopwatch();
            stopwatch3.Start();

            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(person.GetFullName());
            }

            stopwatch3.Stop();

            var sealedPerson = new SealedPerson();

            var stopwatch4 = new Stopwatch();
            stopwatch4.Start();

            for (int i = 0; i < 1000; i++)
            {
                Console.WriteLine(sealedPerson.GetFullName());
            }

            stopwatch4.Stop();

            Console.WriteLine("Instanzierung der Person Class: {0}", stopwatch1.ElapsedMilliseconds);
            Console.WriteLine("Instanzierung der Sealed Person Class: {0}", stopwatch2.ElapsedMilliseconds);

            Console.WriteLine("Methodenaufruf der Person Class: {0}", stopwatch3.ElapsedMilliseconds);
            Console.WriteLine("Methodenaufruf der Sealed Person Class: {0}", stopwatch4.ElapsedMilliseconds);

            Console.ReadLine();
        }
    }

Das Ergebnis zeigt, dass eine Optimierung durch die .NET-Runtime tatsächlich zu existieren scheint. Die Klasse mit Sealed-Schlüsselwort ist performanter, wie im folgenden Bild gut zu erkennen ist.

Der Grund dafür liegt darin, dass C# eine sogenannte „virtual method dispatch table“ nutzt, um Vererbungshierarchien abzubilden und zur Laufzeit nach überschriebenen Methoden darin zu suchen. Für Klassen mit dem Sealed-Schlüsselwort ist das nicht notwendig, was sich in der Performance bemerkbar macht.

In Krzysztof Cwalinas und Brad Abrams „Framework Design Guidelines“ (AddisonWesley) findet man ebenfalls einen hilfreichen Tipp für die Verwendung des Schlüsselwortes im Zusammenhang mit Attributen. Klassen die von Attribute erben, sollten unbedingt als Sealed gekennzeichnet werden, um die Ausführungsgeschwindigkeit zu erhöhen.

Zusammenfassend bleibt festzuhalten, dass die Verwendung des Sealed– Schlüsselwortes aus meiner Sicht anzuraten ist, sofern keine Notwendigkeit besteht, dass andere Klassen von der entsprechenden Klasse erben müssen. Dies lässt sich wiederum leicht durch das Verstehen fachlicher Zusammenhänge erkennen.

Ganz schön „lazy“, was!?

Da gibts doch immer wieder coole Features in C#, die wirklich nützlich sind, welche aber kaum jemand einsetzt. Schon was von Lazy<T> gehört?

Die Lazy<T> -Klasse wurde mit der Frameworkversion 4 eingeführt und dient in erster Linie dazu, Objekte verzögert zu initialisieren. Aus meiner Sicht ist dies ´ne wirklich coole Sache, da man sich damit die Getter von Properties frei von unübersichtlicher Logik halten kann. Die Idee hinter Lazy<T> ist dabei denkbar einfach: Man übergibt dem Konstruktor der Lazy-Klasse einfach einen Delegaten, der beim ersten Zugriff auf die Value-Eigenschaft der Klasse ausgeführt wird. Gedacht ist dies vor allem für Objekte, die entweder sehr ressourcenintensiv sind und/ oder nur in bestimmten Konstellationen des Programmablaufes benötigt werden und deshalb nicht unnötigerweise schon bei der Instanzierung des beherbergenden Objektes geladen werden muss.

Angenommen wir haben eine Klasse Customer und möchten nicht implizit bei jeder Instanzierung des Objektes alle Aufträge mit laden, weil wir die Auflistung beispielsweise recht groß sein kann und nur in bestimmten Situationen benötigt wird,dann hätten wir das bisher wahrscheinlich wie folgt gelöst:

    public class Customer
    {
        private readonly IRepository repository = null;
        private IEnumerable<IOrder> orders = null;

        public Customer(IDataRecord dataRecord, IRepository repository)
        {
            this.repository = repository;
            this.Id = dataRecord.GetGuid(0);
            this.CompanyName = dataRecord.GetString(1);
        }

        public Guid Id { get; private set; }

        public string CompanyName { get; private set; }

        public IEnumerable<IOrder> Orders
        {
            get
            {
                if (this.orders == null)
                {
                    this.orders = 
                        this.repository.GetOrdersByCustomerId(this.Id);
                }

                return this.orders;
            }
        }
    }

Beim Aufruf der Eigenschaft wird geprüft, ob das private Feld schon initialisiert wurde. Wenn nicht, wird das Repository befragt und die Daten werden geladen. Nicht schön daran ist allerdings, dass der Getter ziemlich viel Logik enthält. Ggf. kommt dies ja ähnlich in mehreren Properties vor, so dass der Code dann schnell unleserlich wird.

Eleganter geht das Ganze mit der Lazy>T>-Klasse. Das sieht dann wie folgt aus:

    public class Customer
    {
        private readonly IRepository repository = null;
        private readonly Lazy<IEnumerable<IOrder>> orders = null;

        public Customer(IDataRecord dataRecord, IRepository repository)
        {
            this.repository = repository;
            this.Id = dataRecord.GetGuid(0);
            this.CompanyName = dataRecord.GetString(1);

            this.orders = new Lazy<IEnumerable<IOrder>>(
                () => this.repository.GetOrdersByCustomerId(this.Id));
        }

        public Guid Id { get; private set; }

        public string CompanyName { get; private set; }

        public IEnumerable<IOrder> Orders
        {
            get
            {
                return this.orders.Value;
            }
        }
    }

Die Initialisierungslogik wird uns nun von der Lazy<T>-Klasse abgenommen, so dass wir uns nicht mehr darum kümmern müssen. Sie sorgt zuverlässig dafür, dass der Aufruf gegen das Repository während der Lebenszeit der Customer-Objektes nur einmalig erfolgt. Die Anzahl der Code-Zeilen wurde reduziert und damit die Lesbarkeit erhöht. Richtig bemerkbar macht sich dies natürlich erst, wenn die Klassen größer sind. Guter Stil ist es aus meiner Sicht allemal, die Lazy<T>- Klasse zu verwenden.

Weitere Infos zur Lazy<T>- Klasse gibt es in der MSDN: http://msdn.microsoft.com/de-de/library/dd642331.aspx

Über Feedback, Kritik oder Anregungen zum Blog-Beitrag würde ich mich freuen.

Class oder Struct – Part I

In der letzten Zeit wurde ich einige Mal gefragt, wann man eigentlich eine Klasse und wann besser ein Struct einsetzen sollte. Und, wozu ist denn ein Struct überhaupt gut?

Grund genug, genauer darüber nachzudenken und seit langem wieder Beiträge in meinem Blog zu posten. Um es vorweg zu nehmen: Die Entscheidung, wann welcher Typ eingesetzt werden sollte, ist nicht immer wirklich leicht. Aber genau diese Thematik möchte ich nun diskutieren. Um die Problematik besprechen zu können, muss ich etwas weiter ausholen. Aus diesem Grunde habe ich mich auch dafür entschieden, den Beitrag in zwei Teile zu splitten. Im ersten Teil soll zunächst der Unterschied zwischen Referenz- und Wertetypen näher beleuchtet werden.

Das .net-Framework stellt uns scheinbar zwei sehr ähnliche Typen zur Verfügung, um Strukturen und Verhalten zu implementieren. Dies ist zunächst verwirrend. Bei näherer Betrachtung ist zu erkennen, dass diese beiden gar nicht so ähnlich sind, wie zunächst vermutet. Um den Unterschied zwischen Klasse und Struct zu verstehen, ist es notwendig, das unterschiedliche Verhalten von Referenz- und Wertetypen zu kennen. Die Frage „Klasse oder Struct“ könnte also genauso gut „Referenz- oder Wertetyp“ lauten, denn Klassen sind Referenztypen, während Structs Wertetypen sind.

C# kennt grundsätzlich zwei Kategorien von Typen. Auf der einen Seite sind dies Wertetypen (value type) und auf der andere Seite Referenztypen (reference type). Während Wertetypen ihre Werte direkt enthalten, verweisen Referenztypen auf ihre Werte. Wertetypen in C# sind unter anderem alle einfachen Typen, wie zum Beispiel int, long, decimal usw. Der Zugriff auf Wertetypen erfolgt durch direktes Lesen aus dem Speicher. Legt man eine Kopie an wird ein neuer Eintrag im Speicher erzeugt. Anders bei Referenztypen. Erzeugt man da eine Kopie, wird keine Kopie des Objekts im Speicher gemacht, sondern nur der Verweis weitergereicht. Klassen sind Referenztypen. Als typisches Beispiel für Referenztypen möchte ich hier die String-Klasse des .net-Framework nennen.

Von Wertetypen wird grundsätzlich erwartert, dass sie immutable (unveränderlich) sind, nachdem sie initialisiert wurden. Dies ist auch ein grundlegendes DDD Prinzip, welches von Eric Evans in Domain Driven Design beschrieben wird und eine wichtige Eigenschaft von „ValueObjects“ im Sinne von DDD ist. Veränderbare Wertetypen stellen grundsätzlich ein Problem dar, da der Aufrufer immer eine Kopie, welche implizit erstellt wird, erhält und nicht den Originalwert, was logischerweise auch nicht beeinflusst werden kann.

Um dieses Verhalten besser darzustellen, hier eine Veranschaulichung in Form von Beispielen.

Schlechtes Design eines Struct:

    public struct Rectangle
    {
        public int Width { get; set; }

        public int Length { get; set; }
    }

Die Eigenschaften des Typs Rectangle besitzen öffentliche Setter, die nach der Objektinstanzierung bedient werden müssen.

Der Aufrufer könnte den Typ wie folgt nutzen wollen. Da es sich bei Rectangle um einen Wertetyp handelt, wird nur eine Kopie des Objektes an die Modify-Methode weitergereicht und verändert.

    class Program
    {
        static void Main(string[] args)
        {
            var rectangle = new Rectangle();

            ModifyRectangle(rectangle);

            Console.WriteLine("Width: {0}", rectangle.Width);
            Console.WriteLine("Length: {0}", rectangle.Length);

            Console.ReadLine();
        }

        private static void ModifyRectangle(Rectangle rectangle)
        {
            rectangle.Width = 100;
            rectangle.Length = 200;
        }
    }

Die Ausführung der Anwendung führt zu einem vom Aufrufer sicherlich nicht erwarteten Ergebnis. Von solch einer Struct-Implementierung ist also unbedingt abzuraten. 

Eine korrekte Struct Implementierung sieht so aus:

    public struct Rectangle
    {
        private readonly int width;

        private readonly int length;

        public Rectangle(int width, int length)
        {
            this.width = width;
            this.length = length;
        }

        public int Width 
        { 
            get
            {
                return this.width;
            } 
        }

        public int Length 
        { 
            get
            {
                return this.length;
            } 
        }
    }

Die Eigenschaften besitzen nur Getter. Die Werte müssen vom Aufrufer bei der Objektinstanzierung an den Konstruktor übergeben werden. Die Werte sind unveränderlich.

Der Aufrufer müsste den Typ wie folgt verwenden. Das Setzen sowie Änderungen der Eigenschaften sind von aussen nicht mehr möglich.

        static void Main(string[] args)
        {
            var rectangle = new Rectangle(100, 200);

            Console.WriteLine("Width: {0}", rectangle.Width);
            Console.WriteLine("Length: {0}", rectangle.Length);

            Console.ReadLine();
        }

Im Ergebnis erhält der Aufrufer auch die erwarteten Werte.

Ich hoffe, in dieser Blogausgabe, ein erste, leicht verständliche Einführung in der Betrachtung der Unterscheidung von Klasse und Struct gegeben zu haben. Im nächsten Teil soll es vor allem darum gehen, Beispiele für die sinnvolle Verwendung von Structs zu geben und Entscheidungskriterien für den Einsatz aufzuzeigen.

An Meinungen, Anregungen und Erfahrungen bin ich natürlich sehr interessiert. Also zögert nicht, den Artikel zu kommentieren. Danke.

Artikel in der letzten Ausgabe der Visual Studio One

In der letzten Ausgabe des Fachmagazins Visual Studio One rund um Visual Studio und .NET habe ich einen Artikel zum Thema „Refactoring“ veröffentlicht. Die Visual Studio One stellt den Artikel auf der Webseite als PDF bereit.

http://www.visualstudio1.de/Zufallsartikel1.pdf

oder einfach mal http://www.visualstudio1.de besuchen.

Viel Spaß!