Übergeben von Argumenten an C # generic new () vom Typ Templated


409

Ich versuche, beim Hinzufügen zur Liste ein neues Objekt vom Typ T über seinen Konstruktor zu erstellen.

Ich erhalte einen Kompilierungsfehler: Die Fehlermeldung lautet:

'T': Beim Erstellen einer Instanz einer Variablen können keine Argumente angegeben werden

Aber meine Klassen haben ein Konstruktorargument! Wie kann ich das zum Laufen bringen?

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T(listItem)); // error here.
   } 
   ...
}


2
Vorschlag, diese Funktionalität in die Sprache zu bringen: github.com/dotnet/roslyn/issues/2206
Ian Kemp

Weitere Informationen finden Sie in der Microsoft-Dokumentation unter Compilerfehler CS0417 .
DavidRR

1
Der Vorschlag, diese Funktionalität in die Sprache zu integrieren, wurde verschoben in: github.com/dotnet/csharplang/issues/769
Reduzierung der Aktivität

Antworten:


410

Um eine Instanz eines generischen Typs in einer Funktion zu erstellen, müssen Sie sie mit dem Flag "new" einschränken.

public static string GetAllItems<T>(...) where T : new()

Dies funktioniert jedoch nur, wenn Sie den Konstruktor aufrufen möchten, der keine Parameter hat. Nicht der Fall hier. Stattdessen müssen Sie einen anderen Parameter angeben, mit dem Objekte basierend auf Parametern erstellt werden können. Am einfachsten ist eine Funktion.

public static string GetAllItems<T>(..., Func<ListItem,T> del) {
  ...
  List<T> tabListItems = new List<T>();
  foreach (ListItem listItem in listCollection) 
  {
    tabListItems.Add(del(listItem));
  }
  ...
}

Sie können es dann so nennen

GetAllItems<Foo>(..., l => new Foo(l));

Wie würde dies funktionieren, wenn es intern von einer generischen Klasse aufgerufen wird? Ich habe meinen Code in einer Antwort unten gepostet. Ich kenne die konkrete Klasse intern nicht, da es sich um eine generische Klasse handelt. Gibt es einen Weg, dies zu umgehen? Ich möchte nicht den anderen Vorschlag verwenden, die Eigenschaftsinitialisierersyntax zu verwenden, da dies die Logik
umgeht,

fügte meinen Code zu einer anderen Frage hinzu stackoverflow.com/questions/1682310/…
ChrisCa

21
Dies ist derzeit eine der nervigsten Einschränkungen von C #. Ich möchte meine Klassen unveränderlich machen: Nur private Setter zu haben, würde es unmöglich machen, dass die Klasse aufgrund von Nebenwirkungen in einem ungültigen Zustand ist. Ich benutze auch gerne Func und Lambda, aber ich weiß, dass es immer noch ein Problem in der Geschäftswelt ist, da Programmierer Lambdas im Allgemeinen noch nicht kennen und dies Ihre Klasse schwieriger zu verstehen macht.
Tuomas Hietanen

1
Vielen Dank. In meinem Fall kenne ich die Argumente des Konstruktors, wenn ich die Methode aufrufe . Ich musste nur die Einschränkung des Type-Parameters umgehen, dass er nicht mit Parametern konstruiert werden konnte, also habe ich einen Thunk verwendet . Der Thunk ist ein optionaler Parameter für die Methode, und ich verwende ihn nur, wenn er bereitgestellt wird: T result = thunk == null ? new T() : thunk(); Der Vorteil davon ist für mich, dass die Logik der TErstellung an einem Ort konsolidiert wird, anstatt manchmal Tinnerhalb und manchmal außerhalb der Methode zu erstellen .
Carl G

Ich denke, dies ist einer der Orte, an denen die C # -Sprache beschließt, Nein zum Programmierer zu sagen und nicht mehr immer Ja zu sagen! Obwohl dieser Ansatz ein wenig umständlich ist, um ein Objekt zu erstellen, muss ich ihn erst einmal verwenden.
AmirHossein Rezaei

331

in .Net 3.5 und nachdem Sie die Aktivatorklasse verwenden konnten:

(T)Activator.CreateInstance(typeof(T), args)

1
Wir könnten auch Ausdrucksbaum verwenden, um das Objekt zu erstellen
Welly Tambunan

4
Was ist args? ein Objekt[]?
Rodney P. Barbati

3
Ja, args ist ein Objekt [], in dem Sie die Werte angeben, die dem Konstruktor des T zur Verfügung gestellt werden sollen: "neues Objekt [] {par1, par2}"
TechNyquist


3
WARNUNG: Wenn Sie nur aus Activator.CreateInstancediesem Grund einen dedizierten Konstruktor haben, sieht es so aus, als würde Ihr Konstruktor überhaupt nicht verwendet, und jemand könnte versuchen, ihn zu "bereinigen" und zu löschen (um einen Laufzeitfehler bei zu verursachen einige zufällige Zeit in der Zukunft). Möglicherweise möchten Sie eine Dummy-Funktion hinzufügen, in der Sie diesen Konstruktor verwenden, damit beim Kompilieren ein Kompilierungsfehler angezeigt wird.
jrh

51

Da sich niemand die Mühe gemacht hat, die Antwort "Reflection" zu veröffentlichen (was ich persönlich für die beste Antwort halte), geht es so weiter:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       Type classType = typeof(T);
       ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
       T classInstance = (T)classConstructor.Invoke(new object[] { listItem });

       tabListItems.Add(classInstance);
   } 
   ...
}

Bearbeiten: Diese Antwort ist aufgrund von Activator.CreateInstance von .NET 3.5 veraltet, in älteren .NET-Versionen jedoch weiterhin nützlich.


Meines Wissens nach besteht der größte Teil des Leistungseinbruchs darin, die ConstructorInfo überhaupt zu erwerben. Nimm mein Wort nicht dafür, ohne es zu profilieren. Wenn dies der Fall ist, kann das einfache Speichern der ConstructorInfo zur späteren Wiederverwendung den Leistungseinbruch wiederholter Instanziierungen durch Reflexion verringern.
Kelsie

19
Ich denke, das Fehlen einer Überprüfung der Kompilierungszeit gibt mehr Anlass zur Sorge.
Dave Van den Eynde

1
@ James Ich stimme zu, ich war überrascht, dies nicht als "Antwort" zu sehen. Tatsächlich habe ich nach dieser Frage gesucht und erwartet, ein schönes einfaches Beispiel (wie Ihres) zu finden, da es so lange her ist, dass ich nachgedacht habe. Wie auch immer, +1 von mir, aber +1 auf dem Aktivator antworten auch. Ich habe mir angesehen, was Activator tut, und es stellt sich heraus, dass es sich um eine sehr ausgereifte Reflexion handelt. :)
Mike

Der Aufruf von GetConstructor () ist teuer, daher lohnt es sich, ihn vor der Schleife zwischenzuspeichern. Auf diese Weise ist es viel schneller, wenn Sie nur Invoke () innerhalb der Schleife aufrufen, als beide aufzurufen oder sogar Activator.CreateInstance () zu verwenden.
Cosmin Rus

30

Objektinitialisierer

Wenn Ihr Konstruktor mit dem Parameter nichts anderes tut als eine Eigenschaft festzulegen, können Sie dies in C # 3 oder besser mit einem Objektinitialisierer tun, anstatt einen Konstruktor aufzurufen (was, wie bereits erwähnt, unmöglich ist):

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
   } 
   ...
}

Auf diese Weise können Sie auch jederzeit eine beliebige Konstruktorlogik in den Standardkonstruktor (leer) einfügen.

Activator.CreateInstance ()

Alternativ können Sie Activator.CreateInstance () wie folgt aufrufen :

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
        object[] args = new object[] { listItem };
        tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
   } 
   ...
}

Beachten Sie, dass Activator.CreateInstance einen gewissen Leistungsaufwand verursachen kann , den Sie möglicherweise vermeiden möchten, wenn die Ausführungsgeschwindigkeit oberste Priorität hat und eine andere Option für Sie wartbar ist.


Dies verhindert T, dass die Invarianten geschützt werden ( Tvorausgesetzt, dass > 0 Abhängigkeiten oder erforderliche Werte vorhanden sind, können Sie jetzt Instanzen erstellen T, die sich in einem ungültigen / unbrauchbaren Zustand befinden T.
Sara

20

Sehr alte Frage, aber neue Antwort ;-)

Die ExpressionTree-Version : (Ich denke, die schnellste und sauberste Lösung)

Wie Welly Tambunan sagte: "Wir könnten auch einen Ausdrucksbaum verwenden, um das Objekt zu erstellen."

Dies erzeugt einen 'Konstruktor' (Funktion) für den angegebenen Typ / die angegebenen Parameter. Es gibt einen Delegaten zurück und akzeptiert die Parametertypen als Array von Objekten.

Hier ist es:

// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
    // Get the constructor info for these parameters
    var constructorInfo = type.GetConstructor(parameters);

    // define a object[] parameter
    var paramExpr = Expression.Parameter(typeof(Object[]));

    // To feed the constructor with the right parameters, we need to generate an array 
    // of parameters that will be read from the initialize object array argument.
    var constructorParameters = parameters.Select((paramType, index) =>
        // convert the object[index] to the right constructor parameter type.
        Expression.Convert(
            // read a value from the object[index]
            Expression.ArrayAccess(
                paramExpr,
                Expression.Constant(index)),
            paramType)).ToArray();

    // just call the constructor.
    var body = Expression.New(constructorInfo, constructorParameters);

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
    return constructor.Compile();
}

Beispiel MyClass:

public class MyClass
{
    public int TestInt { get; private set; }
    public string TestString { get; private set; }

    public MyClass(int testInt, string testString)
    {
        TestInt = testInt;
        TestString = testString;
    }
}

Verwendungszweck:

// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

// Call the `myConstructor` function to create a new instance.
var myObject = myConstructor(10, "test message");

Geben Sie hier die Bildbeschreibung ein


Ein weiteres Beispiel: Übergeben der Typen als Array

var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };

// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

DebugView of Expression

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

Dies entspricht dem generierten Code:

public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

Kleiner Nachteil

Alle Wertetypparameter werden eingerahmt, wenn sie wie ein Objektarray übergeben werden.


Einfacher Leistungstest:

private void TestActivator()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message");
    }
    sw.Stop();
    Trace.WriteLine("Activator: " + sw.Elapsed);
}

private void TestReflection()
{
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = constructorInfo.Invoke(new object[] { 10, "test message" });
    }

    sw.Stop();
    Trace.WriteLine("Reflection: " + sw.Elapsed);
}

private void TestExpression()
{
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = myConstructor(10, "test message");
    }

    sw.Stop();
    Trace.WriteLine("Expression: " + sw.Elapsed);
}

TestActivator();
TestReflection();
TestExpression();

Ergebnisse:

Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

Die Verwendung Expressionsist +/- 8-mal schneller als das Aufrufen von ConstructorInfound +/- 20-mal schneller als die Verwendung vonActivator


Haben Sie einen Einblick, was zu tun ist, wenn Sie MyClass <T> mit dem Konstruktor public MyClass (T-Daten) erstellen möchten? In diesem Fall löst Expression.Convert eine Ausnahme aus. Wenn ich die Basisklasse für generische Einschränkungen zum Konvertieren verwende, wird Expression.New ausgelöst, da die Konstruktorinformationen für einen generischen Typ gelten
Mason

@ Mason (hat eine Weile gedauert, um zu antworten ;-)) var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));das funktioniert gut. Ich weiß es nicht.
Jeroen van Langen

19

Dies wird in Ihrer Situation nicht funktionieren. Sie können nur die Einschränkung angeben, dass ein leerer Konstruktor vorhanden ist:

public static string GetAllItems<T>(...) where T: new()

Sie können die Eigenschaftsinjektion verwenden, indem Sie diese Schnittstelle definieren:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Dann könnten Sie Ihre Methode folgendermaßen ändern:

public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { Item = listItem });
   } 
   ...
}

Die andere Alternative ist die Funcvon JaredPar beschriebene Methode.


Dies würde jede Logik umgehen, die sich im Konstruktor befindet, der die Argumente akzeptiert, oder? Ich würde gerne etwas wie Jareds Ansatz machen, rufe aber die Methode intern innerhalb der Klasse auf, also weiß ich nicht, was der konkrete Typ ist ... hmmm
ChrisCa

3
Richtig, dies ruft die Logik des Standardkonstruktors T () auf und setzt dann einfach die Eigenschaft "Item". Wenn Sie versuchen, die Logik eines nicht standardmäßigen Konstruktors aufzurufen, hilft Ihnen dies nicht weiter.
Scott Stafford

7

Sie müssen hinzufügen, wo T: new (), um den Compiler wissen zu lassen, dass T garantiert einen Standardkonstruktor bereitstellt.

public static string GetAllItems<T>(...) where T: new()

1
UPDATE: Die richtige Fehlermeldung lautet: 'T': Beim Erstellen einer Instanz einer Variablen
LB

Das liegt daran, dass Sie keinen leeren Konstruktor verwenden, sondern ein Argument für das Objekt übergeben. Es gibt keine Möglichkeit, damit umzugehen, ohne anzugeben, dass der generische Typ einen neuen (Objekt-) Parameter hat.
Min

Dann müssen Sie entweder: 1. Reflektion verwenden 2. Den Parameter an eine Initialisierungsmethode anstelle des Konstruktors übergeben, wobei die Initialisierungsmethode zu einer Schnittstelle gehört, die Ihr Typ implementiert und die in where T: ... enthalten ist. Erklärung. Option 1 hat die geringste Auswirkung auf den Rest Ihres Codes, Option 2 bietet jedoch eine Überprüfung der Kompilierungszeit.
Richard

Verwenden Sie keine Reflexion! Es gibt andere Möglichkeiten, wie in anderen Antworten beschrieben, mit denen Sie den gleichen Effekt erzielen.
Garry Shutler

@Garry - Ich würde zustimmen, dass Reflexion nicht unbedingt der beste Ansatz ist, aber es ermöglicht Ihnen, das zu erreichen, was mit minimalen Änderungen an der restlichen Codebasis erforderlich ist. Trotzdem bevorzuge ich den Factory Delegate-Ansatz von @JaredPar.
Richard

7

Wenn Sie einfach ein Elementfeld oder eine Eigenschaft mit dem Konstruktorparameter initialisieren möchten, können Sie dies in C #> = 3 ganz einfach tun:

public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new() 
{ 
   ... 
   List<T> tabListItems = new List<T>(); 
   foreach (ListItem listItem in listCollection)  
   { 
       tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass. 
   }  
   ... 
} 

Dies ist das gleiche, was Garry Shutler gesagt hat, aber ich möchte eine zusätzliche Notiz machen.

Natürlich können Sie einen Eigenschaftstrick verwenden, um mehr zu tun als nur einen Feldwert festzulegen. Eine Eigenschaft "set ()" kann jede Verarbeitung auslösen, die zum Einrichten der zugehörigen Felder erforderlich ist, sowie jede andere Notwendigkeit für das Objekt selbst, einschließlich einer Überprüfung, ob eine vollständige Initialisierung stattfinden soll, bevor das Objekt verwendet wird, und eine vollständige Konstruktion simulieren ( Ja, es ist eine hässliche Problemumgehung, aber es überwindet die neue () Einschränkung von M $.

Ich kann nicht sicher sein, ob es sich um ein geplantes Loch oder eine versehentliche Nebenwirkung handelt, aber es funktioniert.

Es ist sehr lustig, wie MS-Leute der Sprache neue Funktionen hinzufügen und keine vollständige Analyse der Nebenwirkungen durchführen. Die gesamte generische Sache ist ein guter Beweis dafür ...


1
Beide Einschränkungen sind erforderlich. InterfaceOrBaseClass macht den Compiler auf das Feld / die Eigenschaft BaseMemberItem aufmerksam. Wenn die Einschränkung "new ()" kommentiert wird, wird der Fehler ausgelöst: Fehler 6 Es kann keine Instanz des Variablentyps 'T' erstellt werden, da die Einschränkung new () nicht vorhanden ist
fljx

Eine Situation, der ich begegnet bin, entsprach nicht genau der hier gestellten Frage, aber diese Antwort brachte mich dahin, wo ich hin musste, und sie scheint sehr gut zu funktionieren.
RubyHaus

5
Jedes Mal, wenn jemand Microsoft als "M $" erwähnt, leidet ein winziger Teil meiner Seele.
Mathias Lykkegaard Lorenzen

6

Ich habe festgestellt, dass die Fehlermeldung "Beim Erstellen einer Instanz des Typparameters T kann keine Argumente angegeben werden" angezeigt wird. Daher musste ich Folgendes tun:

var x = Activator.CreateInstance(typeof(T), args) as T;

5

Wenn Sie Zugriff auf die Klasse haben, die Sie verwenden möchten, können Sie diesen Ansatz verwenden, den ich verwendet habe.

Erstellen Sie eine Schnittstelle mit einem alternativen Ersteller:

public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

Erstellen Sie Ihre Klassen mit einem leeren Ersteller und implementieren Sie diese Methode:

public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

Verwenden Sie jetzt Ihre generischen Methoden:

public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

Wenn Sie keinen Zugriff haben, schließen Sie die Zielklasse ein:

public class MyClass : ICreatable1Param
{
    public WrappedClass WrappedInstance {get; private set; }
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        WrappedInstance = new WrappedClass(Param);
    }
}

0

Das ist ein bisschen mies, und wenn ich ein bisschen mucky sage, meine ich vielleicht abstoßend, aber wenn Sie Ihren parametrisierten Typ mit einem leeren Konstruktor ausstatten können, dann:

public static T GetTInstance<T>() where T: new()
{
    var constructorTypeSignature = new Type[] {typeof (object)};
    var constructorParameters = new object[] {"Create a T"};
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}

Ermöglicht es Ihnen effektiv, ein Objekt aus einem parametrisierten Typ mit einem Argument zu erstellen. In diesem Fall gehe ich davon aus, dass der gewünschte Konstruktor ein einziges Argument vom Typ hat object. Wir erstellen eine Dummy-Instanz von T unter Verwendung des leeren Konstruktors für die zulässige Einschränkung und verwenden dann die Reflexion, um einen seiner anderen Konstruktoren zu erhalten.


0

Ich verwende manchmal einen Ansatz, der den Antworten mit Eigenschaftsinjektion ähnelt, aber den Code sauberer hält. Anstatt eine Basisklasse / Schnittstelle mit einer Reihe von Eigenschaften zu haben, enthält sie nur eine (virtuelle) Initialize () - Methode, die als "Konstruktor des armen Mannes" fungiert. Dann können Sie jede Klasse ihre eigene Initialisierung wie einen Konstruktor ausführen lassen, was auch eine bequeme Möglichkeit zum Umgang mit Vererbungsketten bietet.

Wenn ich mich häufig in Situationen befinde, in denen jede Klasse in der Kette ihre eindeutigen Eigenschaften initialisieren soll, rufen Sie dann die Initialize () - Methode des übergeordneten Elements auf, die wiederum die eindeutigen Eigenschaften des übergeordneten Elements usw. initialisiert. Dies ist besonders nützlich, wenn unterschiedliche Klassen vorhanden sind, jedoch eine ähnliche Hierarchie aufweisen, z. B. Geschäftsobjekte, die DTOs zugeordnet sind: s.

Beispiel, das ein gemeinsames Wörterbuch zur Initialisierung verwendet:

void Main()
{
    var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } };

    Console.WriteLine(CreateObject<Base>(values).ToString());

    Console.WriteLine(CreateObject<Derived>(values).ToString());
}

public T CreateObject<T>(IDictionary<string, int> values)
    where T : Base, new()
{
    var obj = new T();
    obj.Initialize(values);
    return obj;
}

public class Base
{
    public int BaseValue { get; set; }

    public virtual void Initialize(IDictionary<string, int> values)
    {
        BaseValue = values["BaseValue"];
    }

    public override string ToString()
    {
        return "BaseValue = " + BaseValue;
    }
}

public class Derived : Base
{
    public int DerivedValue { get; set; }

    public override void Initialize(IDictionary<string, int> values)
    {
        base.Initialize(values);
        DerivedValue = values["DerivedValue"];
    }

    public override string ToString()
    {       
        return base.ToString() + ", DerivedValue = " + DerivedValue;
    }
}

0

Wenn Sie lediglich eine Konvertierung von ListItem in Ihren Typ T benötigen, können Sie diese Konvertierung in der Klasse T als Konvertierungsoperator implementieren.

public class T
{
    public static implicit operator T(ListItem listItem) => /* ... */;
}

public static string GetAllItems(...)
{
    ...
    List<T> tabListItems = new List<T>();
    foreach (ListItem listItem in listCollection) 
    {
        tabListItems.Add(listItem);
    } 
    ...
}

-4

Ich glaube, Sie müssen T mit einer where-Anweisung einschränken, um nur Objekte mit einem neuen Konstruktor zuzulassen.

Jetzt akzeptiert es alles, einschließlich Objekte ohne es.


1
Möglicherweise möchten Sie diese Antwort ändern, da diese nach der Beantwortung in der Frage bearbeitet wurde, wodurch diese Antwort nicht mehr im Kontext steht.
Shuttle87
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.