Entwickler-Ecke

Sonstiges (.NET) - statische Funktion, die eine Instanz erzeugt vs Konstruktor


C# - Mi 10.12.14 16:11
Titel: statische Funktion, die eine Instanz erzeugt vs Konstruktor
Hey,

ich stelle mir gerade die Frage, warum es (z.B. im .NET-Framework) Typen gibt, die sich über statische Funktionen instanziieren lassen, anstatt dafür einen Konstruktor zu verwenden. Spontan fällt mir da z.B. die Graphics-Klasse aus WinForms ein, welche die Funktion Graphisc.FromImage(einBild) anbietet. Warum wird sowas nicht in einen Konstruktor gepackt, also new Graphics(einBild);? Gibt es da irgendwelche Vorteile oder warum wird sowas gemacht?


Blup - Mi 10.12.14 16:28

Dahinter verbirgt sich meist eine Factory die für deine Anwendung die geeignete Bild-Klasse erzeugt.
Du könntest z.B. deine eigene Bildklasse (oder eine fremde) bei der Factory registrieren, die verbesserte Darstellung beim Zoomen liefert.
Deine ganze Anwendung profitiert davon, ohne eine Zeile im eigentlichen Quellcode zu ändern.
Selbst fremde Komponenten, die fertig vorliegen und eine Bild-Instance erstellen, müssen nicht verändert werden.


Ralf Jansen - Mi 10.12.14 16:29

Ich würde es machen um über die statische Methode eine Ableitung des zurückgegebenen Typs zu erzeugen von der der User nix genaues Wissen muß. Je nach Context liefert die Methode dann halt eine anderen Typ. Ich glaube bei Graphcs ist das aber nicht der Fall.


C# - Mi 10.12.14 19:37

@ Blub
Zitat:
Dahinter verbirgt sich meist eine Factory die für deine Anwendung die geeignete Bild-Klasse erzeugt.

Ja aber das kann doch genau so gut im Konstruktor passieren.
Color.FromArgb(...) fällt mir gerade noch als Beispiel ein.

@ Ralf
Verstehe ich dich richtig: du hast eine statische Methode in einer Klasse A. Diese Methode erzeugt - je nach Wert der Parameter - ein Objekt vom Typ B, welcher von A abgeleitet wird? Das würde dann aber bedeuten, dass die Basisklasse (A) alle Ableitungen kennen müsste (B). Außerdem wäre ja dann die Methode in einer anderen Klasse definiert.


Was macht es für einen Sinn wenn ich innerhalb der gleichen Klasse einmal einen Konstruktor habe um das Objekt zu erzeugen und einmal eine statische Methode die das gleiche Objekt erzeugt?
Noch ein Beispiel:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
 class Vector
    {
        public int X, Y;

        public Vector(int x, int y)
        {
            X = x;
            Y = y;
        }

        // Warum
        public static Vector FromPoint(Point p)
        {
            return new Vector(p.X, p.Y);
        }

        // statt
        public Vector(Point p) : this(p.X, p.Y)
        {
            
        }
    }


jfheins - Mi 10.12.14 20:23

Dafür gibt es viele Gründe. Einer ist zum Beispiel die Philosophie "Konstruktorparameter ains essenziell für das Objekt und ohne diese Werte macht das Objekt einfach keinen Sinn". Darein fallen viele Sachen dann eben nicht, insbesondere viele FromXYZ Methoden. Sie bieten lediglich einen Weg an, Daten zu importieren.
Beispiel:

C#-Quelltext
1:
2:
3:
4:
5:
6:
    public static Hit FromProperty<T>(Expression<Func<T, object>> propertyExpression, string text)
      where T : class
    {
      var name = AttributeHelperMethods.GetPropertyDisplayName(propertyExpression);
      return new Hit(name, text);
    }

Die Methode ist generisch. Das ginge mit dem Konstruktor nicht, da nur der Konstruktor einer generischen Klasse generisch sein kann.
Vorteil: Am Naman der Methode ist direkt dokumentiert, welchen Zweck sie erfüllt. Stell' dir vor, es gäbe diese hier:

C#-Quelltext
1:
2:
3:
4:
5:
Color.FromRGB()
Color.FromARGB()
Color.FromHSV()
Color.FromCMYK()
Color.FromCieLab()

Das kannst du gar nicht ordentlich mit Konstruktorparametern abbilden, da es alles 3-4 int-Werte sind.


Ralf Jansen - Mi 10.12.14 21:09

Zitat:
@ Ralf
Verstehe ich dich richtig: du hast eine statische Methode in einer Klasse A. Diese Methode erzeugt - je nach Wert der Parameter - ein Objekt vom Typ B, welcher von A abgeleitet wird? Das würde dann aber bedeuten, dass die Basisklasse (A) alle Ableitungen kennen müsste (B). Außerdem wäre ja dann die Methode in einer anderen Klasse definiert.


Die B's können im Zweifel auch irgendwas dynamisches sein was zur Compilezeit noch gar nicht bekannt ist (z.B. weil es ein Plugin System ist). Aber man braucht natürlich einen Ansatzpunkt um an konkrete Klassen zu kommen ohne die konkret zu kennen. Da ist eine Create Methode an einer Basisklasse ein möglicher Weg.

Solche Create Methoden sind ein übliches Muster im Framework (ohne dabei irgendwas mit Plugins zu tun zu haben) Beispiele :

- Delegate.CreateDelegate (liefert MulticastDelegate)
- Array.CreateInstance (liefert konkretes Array abhängig vom übergebenen Typ)
- im Cryptography Namespace haben eigentlich alle Basistypen eine Create Methode der man den konkret gewünschten Algo (der eine Ableitung der Basisklasse darstellt) mitgeben kann

Zitat:
Ja aber das kann doch genau so gut im Konstruktor passieren.
Color.FromArgb(...) fällt mir gerade noch als Beispiel ein.


Ein Konstruktor hat wie jfheins nebenbei aufzeigt extrem schlechte dokumentarische Eigenschaften. Gerade bei Color ist es eben nicht genauso lesbar machbar. Da Graphics eine ganze Reihe von FromXXX Methoden hat mag das dort auch ein Grund sein.

Meine Theorien nach 2.ter Überlegung bezüglich Graphics ist eine andere ;)
Die Methode hat erst im 2.0er Framework Einzug gehalten so wie die anderen FromXXX Methoden. Die 1er Philosophie von Graphics ist aber vermutlich das man die nicht selbst erzeugen können soll.
Ein Graphics Object macht ja auch isoliert keinen Sinn es ist immer nur ein Wrapper um einen GDI Context der an einem Control hängt und man holt sich den vom Control (Control.CreateGraphics) und erzeugt den eben nicht selber. Das man den auch für ein Image gebrauchen kann ist denen dann erst später eingefallen da stand die Philosophie aber schon. Beim Designermeeting Graphics.FromImage vs. Image.CreateGraphics hat sich dann erstere Partei durchgesetzt. Die Überlegung könnt aber auch nur eine Verschwörungtheorie sein ;)
Worauf ich hinaus will im Rückblick auf ein Design ist es leicht historische Notwendigkeiten (ich wage es nicht da von Schwäche zu sprechen über die Stabilität der Designphilisophie der ursprünglich erdachten Muster kann ich nur staunen) zu übersehen die sich oft nur den direkt Beteiligten erschließen. Wir können nur hoffen das ein paar Microsoft Insider in ihren Blogs über solche Details schreiben und uns den Entstehungsweg eines Features im Framework aufzeigen.


C# - Mi 10.12.14 23:42

Ja das leuchtet ein. Vielen dank Euch.