Was sind die Verwendungszwecke von "using" in C #?


318

Benutzer kokos beantwortete die wunderbare Frage nach den versteckten Funktionen von C # mit dem usingSchlüsselwort. Können Sie das näher erläutern? Wofür werden sie verwendet using?


Es ist eine C # -Methode
Nemanja Trifunovic

1
Sie können Objekte verwenden, für die eine IDispose-Schnittstelle implementiert ist. Mit wird die Dispose-Methode aufgerufen, wenn das Objekt den Gültigkeitsbereich verlässt. Es garantiert, dass Dispose aufgerufen wird, auch wenn eine Ausnahme auftritt. Es funktioniert wie eine finally-Klausel und führt Dispose aus.
CharithJ

Antworten:


479

Der Grund für die usingAnweisung besteht darin, sicherzustellen, dass das Objekt entsorgt wird, sobald es den Gültigkeitsbereich verlässt, und dass kein expliziter Code erforderlich ist, um sicherzustellen, dass dies geschieht.

Wie unter Grundlegendes zur Anweisung 'using' in C # (Codeprojekt) und Verwenden von Objekten, die IDisposable (Microsoft) implementieren, konvertiert der C # -Compiler

using (MyResource myRes = new MyResource())
{
    myRes.DoSomething();
}

zu

{ // Limits scope of myRes
    MyResource myRes= new MyResource();
    try
    {
        myRes.DoSomething();
    }
    finally
    {
        // Check for a null resource.
        if (myRes != null)
            // Call the object's Dispose method.
            ((IDisposable)myRes).Dispose();
    }
}

C # 8 führt eine neue Syntax mit dem Namen " using declarations " ein:

Eine using-Deklaration ist eine Variablendeklaration, der das using-Schlüsselwort vorangestellt ist. Es teilt dem Compiler mit, dass die deklarierte Variable am Ende des umschließenden Bereichs entsorgt werden soll.

Der äquivalente Code von oben wäre also:

using var myRes = new MyResource();
myRes.DoSomething();

Und wenn die Steuerung den enthaltenen Bereich verlässt (normalerweise eine Methode, kann aber auch ein Codeblock sein), myReswird sie entsorgt.


129
Beachten Sie, dass es nicht unbedingt darum geht, dass das Objekt korrekt entsorgt wird , sondern vielmehr darum , ob es rechtzeitig entsorgt wird. Objekte, die IDisposable implementieren und nicht verwaltete Ressourcen wie Streams und Dateihandles beibehalten, implementieren auch einen Finalizer, der sicherstellt, dass Dispose während der Speicherbereinigung aufgerufen wird. Das Problem ist, dass GC möglicherweise relativ lange nicht auftritt. usingstellt sicher, dass dies Disposeaufgerufen wird, sobald Sie mit dem Objekt fertig sind.
John Saunders

1
Bitte beachten Sie, dass der generierte Code etwas anders ist, wenn MyRessourcees sich um eine Struktur handelt. Es gibt offensichtlich keinen Test für die Nichtigkeit, aber auch kein Boxen zu IDisposable. Ein eingeschränkter virtueller Anruf wird ausgegeben.
Romain Verdier

4
Warum erwähnt niemand, dass using auch zum Importieren von Namespaces verwendet wird?
Kyle Delaney

3
Beachten Sie, dass das Ergebnis nicht dasselbe ist, wenn Sie die zweite Version des Codes direkt schreiben. Wenn Sie die verwenden using, ist die darin erstellte Variable schreibgeschützt. Ohne die usingAnweisung ist dies für lokale Variablen nicht möglich .
Massimiliano Kraus

1
@JohnSaunders Außerdem wird nicht garantiert, dass der Finalizer aufgerufen wird.
Pablo H

124

Da viele Leute immer noch tun:

using (System.IO.StreamReader r = new System.IO.StreamReader(""))
using (System.IO.StreamReader r2 = new System.IO.StreamReader("")) {
   //code
}

Ich denke, viele Leute wissen immer noch nicht, dass Sie Folgendes tun können:

using (System.IO.StreamReader r = new System.IO.StreamReader(""), r2 = new System.IO.StreamReader("")) {
   //code
}

2
Ist es möglich, mehrere Objekte unterschiedlichen Typs in einer einzigen using-Anweisung zu verwenden?
Agnel Kurian

12
@AgnelKurian Nein: "Fehler CS1044: Es kann nicht mehr als ein Typ in einer for-, using-, fixed- oder deklarationsanweisung verwendet werden"
David Sykes

10
Wie beantwortet dies die Frage?
Liam

Ich wusste eigentlich nicht, dass ich zwei mit statemens vor einem einzelnen Codeblock schreiben kann (würde sie jedes Mal verschachteln).
kub1x

97

Dinge wie dieses:

using (var conn = new SqlConnection("connection string"))
{
   conn.Open();

    // Execute SQL statement here on the connection you created
}

Dies SqlConnectionwird geschlossen, ohne dass die .Close()Funktion explizit aufgerufen werden muss, und dies geschieht auch dann, wenn eine Ausnahme ausgelöst wird , ohne dass a erforderlich isttry / catch/ finally.


1
Was ist, wenn ich ein "using" innerhalb einer Methode verwende und mitten in einem using zurückkehre? Gibt es ein Problem?
francisco_ssb

1
Da gibt es kein Problem. Im Beispiel hier bleibt die Verbindung auch dann geschlossen, wenn Sie sich returnin der Mitte des usingBlocks befinden.
Joel Coehoorn

30

Mit using kann IDisposable aufgerufen werden. Es kann auch für Alias-Typen verwendet werden.

using (SqlConnection cnn = new SqlConnection()) { /*code*/}
using f1 = System.Windows.Forms.Form;

21

mit im Sinne von

using (var foo = new Bar())
{
  Baz();
}

Ist eigentlich Abkürzung für einen try / finally-Block. Es entspricht dem Code:

var foo = new Bar();
try
{
  Baz();
}
finally
{
  foo.Dispose();
}

Sie werden natürlich feststellen, dass das erste Snippet viel prägnanter ist als das zweite und dass es viele Arten von Dingen gibt, die Sie möglicherweise als Bereinigung ausführen möchten, selbst wenn eine Ausnahme ausgelöst wird. Aus diesem Grund haben wir eine Klasse entwickelt, die wir Scope nennen und mit der Sie beliebigen Code in der Dispose-Methode ausführen können. Wenn Sie beispielsweise eine Eigenschaft namens IsWorking hatten, die Sie nach dem Versuch, eine Operation auszuführen, immer auf false setzen wollten, gehen Sie folgendermaßen vor:

using (new Scope(() => IsWorking = false))
{
  IsWorking = true;
  MundaneYetDangerousWork();
}

Lesen Sie hier mehr über unsere Lösung und wie wir sie abgeleitet haben .


12

In der Microsoft-Dokumentation heißt es, dass die Verwendung eine Doppelfunktion hat ( https://msdn.microsoft.com/en-us/library/zhdeatwt.aspx ), sowohl als Direktive als auch in Anweisungen . Als Aussage , wie hier in anderen Antworten ausgeführt wurde, ist das Schlüsselwort im Wesentlichen syntaktischer Zucker, um einen Bereich zum Entsorgen eines IDisposable- Objekts zu bestimmen . Als Direktive wird sie routinemäßig zum Importieren von Namespaces und Typen verwendet. Auch als Direktive können Sie Aliase erstellen für Namespaces und Typen , wie im Buch "C # 5.0 auf den Punkt gebracht: Der endgültige Leitfaden" ( http://www.amazon.com/5-0-Nutshell-The-) beschrieben. Definitive-Reference-ebook / dp / B008E6I1K8), von Joseph und Ben Albahari. Ein Beispiel:

namespace HelloWorld
{
    using AppFunc = Func<IDictionary<DateTime, string>, List<string>>;
    public class Startup
    {
        public static AppFunc OrderEvents() 
        {
            AppFunc appFunc = (IDictionary<DateTime, string> events) =>
            {
                if ((events != null) && (events.Count > 0))
                {
                    List<string> result = events.OrderBy(ev => ev.Key)
                        .Select(ev => ev.Value)
                        .ToList();
                    return result;
                }
                throw new ArgumentException("Event dictionary is null or empty.");
            };
            return appFunc;
        }
    }
}

Dies ist etwas, das mit Bedacht angewendet werden sollte, da der Missbrauch dieser Praxis die Klarheit des eigenen Codes beeinträchtigen kann. In DotNetPearls ( http://www.dotnetperls.com/using-alias ) gibt es eine nette Erklärung zu C # -Aliasen, in der auch Vor- und Nachteile erwähnt werden .


4
Ich werde nicht lügen: Ich hasse die Verwendung usingals Alias-Tool. Es verwirrt mich beim Lesen des Codes - ich weiß bereits, dass das System.Collectionsexistiert und die IEnumerable<T>Klasse hat. Die Verwendung eines Alias, um es etwas anderes zu nennen, verschleiert es für mich. Ich sehe using FooCollection = IEnumerable<Foo>eine Möglichkeit, spätere Entwickler dazu zu bringen, den Code zu lesen und zu denken: "Was zum Teufel ist ein FooCollectionund warum gibt es nicht irgendwo eine Klasse dafür?" Ich benutze es nie und würde von seiner Verwendung abraten. Aber das könnte nur ich sein.
Ari Roth

1
Nachtrag: Ich gebe zu, dass es gelegentlich eine Verwendung dafür geben könnte, wie in Ihrem Beispiel, in dem Sie damit einen Delegaten definieren. Aber ich würde behaupten, dass diese relativ selten sind.
Ari Roth

10

Ich habe es in der Vergangenheit oft benutzt, um mit Eingabe- und Ausgabestreams zu arbeiten. Sie können sie gut verschachteln und es werden viele der potenziellen Probleme beseitigt, auf die Sie normalerweise stoßen (indem Sie automatisch dispose aufrufen). Zum Beispiel:

        using (FileStream fs = new FileStream("c:\file.txt", FileMode.Open))
        {
            using (BufferedStream bs = new BufferedStream(fs))
            {
                using (System.IO.StreamReader sr = new StreamReader(bs))
                {
                    string output = sr.ReadToEnd();
                }
            }
        }

8

Nur ein bisschen etwas hinzuzufügen, das mich überraschte, kam nicht auf. Das interessanteste Merkmal der Verwendung (meiner Meinung nach) ist, dass unabhängig davon, wie Sie den using-Block verlassen, das Objekt immer entsorgt wird. Dies schließt Rückgaben und Ausnahmen ein.

using (var db = new DbContext())
{
    if(db.State == State.Closed) throw new Exception("Database connection is closed.");
    return db.Something.ToList();
}

Es spielt keine Rolle, ob die Ausnahme ausgelöst oder die Liste zurückgegeben wird. Das DbContext-Objekt wird immer entsorgt.


6

Eine weitere großartige Verwendung ist das Instanziieren eines modalen Dialogs.

Using frm as new Form1

Form1.ShowDialog

' do stuff here

End Using

1
Meinten Sie frm.ShowDialog?
UuDdLrLrSs

5

Abschließend , wenn Sie eine lokale Variable eines Typs verwenden , dass Geräte IDisposable, immer , ohne Ausnahme, benutzen using1 .

Wenn Sie nichtlokale IDisposableVariablen verwenden, implementieren Sie immer das IDisposableMuster .

Zwei einfache Regeln, keine Ausnahme 1 . Das Verhindern von Ressourcenlecks auf andere Weise ist ein echtes Problem.


1) : Die einzige Ausnahme ist - wenn Sie Ausnahmen behandeln. Es könnte dann weniger Code sein, um Disposeexplizit im finallyBlock aufzurufen .


5

Sie können den Alias-Namespace anhand des folgenden Beispiels verwenden:

using LegacyEntities = CompanyFoo.CoreLib.x86.VBComponents.CompanyObjects;

Wie Sie sehen, wird dies als Alias-Direktive verwendet. Sie kann verwendet werden, um langwierige Verweise auszublenden, wenn Sie in Ihrem Code deutlich machen möchten, worauf Sie sich beziehen, z

LegacyEntities.Account

anstatt

CompanyFoo.CoreLib.x86.VBComponents.CompanyObjects.Account

oder einfach

Account   // It is not obvious this is a legacy entity

4

Interessanterweise können Sie das using / IDisposable-Muster auch für andere interessante Dinge verwenden (z. B. für den anderen Punkt, an dem Rhino Mocks es verwendet). Grundsätzlich können Sie die Tatsache nutzen, dass der Compiler immer wird .Dispose für das "verwendete" Objekt aufruft. Wenn Sie etwas haben, das nach einer bestimmten Operation geschehen muss ... etwas, das einen bestimmten Anfang und ein bestimmtes Ende hat ..., können Sie einfach eine IDisposable-Klasse erstellen, die die Operation im Konstruktor startet und dann in der Dispose-Methode beendet.

Auf diese Weise können Sie die wirklich nette Syntax verwenden, um den expliziten Start und das Ende dieser Operation zu kennzeichnen. So funktioniert auch das System.Transactions-Zeug.


3

Wenn Sie ADO.NET verwenden, können Sie das Keywork für Dinge wie Ihr Verbindungsobjekt oder Leserobjekt verwenden. Auf diese Weise wird Ihre Verbindung automatisch gelöscht, wenn der Codeblock abgeschlossen ist.


2
Ich möchte nur hinzufügen, dass der Codeblock nicht einmal vervollständigt werden muss. Ein using-Block entsorgt die Ressource auch im Falle einer nicht behandelten Ausnahme.
Harpo

Um dies weiter zu verdeutlichen, können Sie auf diese Weise sicherstellen, dass der Garbage Collector sich um Ihre Zuordnungen kümmert, wenn Sie dies möchten, anstatt dies zu tun, wenn dies gewünscht wird .
Moswald


3
public class ClassA:IDisposable

{
   #region IDisposable Members        
    public void Dispose()
    {            
        GC.SuppressFinalize(this);
    }
    #endregion
}

public void fn_Data()

    {
     using (ClassA ObjectName = new ClassA())
            {
                //use objectName 
            }
    }

2

using wird verwendet, wenn Sie über eine Ressource verfügen, die nach ihrer Verwendung entsorgt werden soll.

Wenn Sie beispielsweise eine Dateiressource zuweisen und sie nur in einem Codeabschnitt zum Lesen oder Schreiben verwenden müssen, ist die Verwendung hilfreich, um die Dateiressource zu entsorgen, sobald Sie fertig sind.

Die verwendete Ressource muss IDisposable implementieren, damit sie ordnungsgemäß funktioniert.

Beispiel:

using (File file = new File (parameters))
{
    *code to do stuff with the file*
}

1

Das Schlüsselwort using definiert den Bereich für das Objekt und entsorgt das Objekt, wenn der Bereich vollständig ist. Zum Beispiel.

using (Font font2 = new Font("Arial", 10.0f))
{
    // use font2
}

Sehen Sie hier für den MSDN - Artikel auf dem C # Schlüsselwort.


1

Nicht, dass es extrem wichtig wäre, aber die Verwendung kann auch verwendet werden, um Ressourcen im laufenden Betrieb zu ändern. Ja, wie bereits erwähnt verfügbar, aber möglicherweise möchten Sie nicht, dass die Ressourcen während des Restes Ihrer Ausführung nicht mit anderen Ressourcen übereinstimmen. Sie möchten es also entsorgen, damit es anderswo nicht stört.


1

Dank der folgenden Kommentare werde ich diesen Beitrag ein wenig bereinigen (ich hätte zu diesem Zeitpunkt nicht die Worte 'Garbage Collection' verwenden sollen, entschuldige
mich ): Wenn Sie using verwenden, wird die Dispose () -Methode für das Objekt aufgerufen am Ende des Anwendungsbereichs. Sie können also in Ihrer Dispose () -Methode eine Menge großartigen Bereinigungscodes haben.
Ein Aufzählungspunkt hier, der hoffentlich zu diesem nicht markierten Ergebnis führen wird: Wenn Sie IDisposable implementieren, stellen Sie sicher, dass Sie GC.SuppressFinalize () in Ihrer Dispose () -Implementierung aufrufen, da andernfalls die automatische Speicherbereinigung versucht, dies zu erreichen und bei einigen zu finalisieren Punkt, der zumindest eine Verschwendung von Ressourcen wäre, wenn Sie bereits () d davon entsorgen ().


Es hat eine indirekte Wirkung. Da Sie das Objekt explizit entsorgt haben, ist keine Finalisierung erforderlich und kann daher früher durchgeführt werden.
Kent Boogaart

1

Ein weiteres Beispiel für eine sinnvolle Verwendung, bei der das Objekt sofort entsorgt wird:

using (IDataReader myReader = DataFunctions.ExecuteReader(CommandType.Text, sql.ToString(), dp.Parameters, myConnectionString)) 
{
    while (myReader.Read()) 
    {
        MyObject theObject = new MyObject();
        theObject.PublicProperty = myReader.GetString(0);
        myCollection.Add(theObject);
    }
}

1

Alles außerhalb der geschweiften Klammern ist entsorgt, daher ist es großartig, Ihre Objekte zu entsorgen, wenn Sie sie nicht verwenden. Dies liegt daran, dass Sie den folgenden Code verwenden können, wenn Sie ein SqlDataAdapter-Objekt haben und es nur einmal im Anwendungslebenszyklus verwenden und nur einen Datensatz füllen und ihn nicht mehr benötigen:

using(SqlDataAdapter adapter_object = new SqlDataAdapter(sql_command_parameter))
{
   // do stuff
} // here adapter_object is disposed automatically

1

Die using-Anweisung bietet einen praktischen Mechanismus zur korrekten Verwendung von IDisposable-Objekten. Wenn Sie ein IDisposable-Objekt verwenden, sollten Sie es in der Regel in einer using-Anweisung deklarieren und instanziieren. Die using-Anweisung ruft die Dispose-Methode für das Objekt auf die richtige Weise auf und bewirkt (wenn Sie sie wie oben gezeigt verwenden), dass das Objekt selbst den Gültigkeitsbereich verlässt, sobald Dispose aufgerufen wird. Innerhalb des using-Blocks ist das Objekt schreibgeschützt und kann nicht geändert oder neu zugewiesen werden.

Das kommt von: hier


1

Für mich ist der Name "using" etwas verwirrend, da er eine Anweisung zum Importieren eines Namespace oder einer Anweisung (wie die hier beschriebene) zur Fehlerbehandlung sein kann.

Ein anderer Name für die Fehlerbehandlung wäre nett gewesen und vielleicht ein offensichtlicherer.


1

Es kann auch zum Erstellen von Bereichen verwendet werden, zum Beispiel:

class LoggerScope:IDisposable {
   static ThreadLocal<LoggerScope> threadScope = 
        new ThreadLocal<LoggerScope>();
   private LoggerScope previous;

   public static LoggerScope Current=> threadScope.Value;

   public bool WithTime{get;}

   public LoggerScope(bool withTime){
       previous = threadScope.Value;
       threadScope.Value = this;
       WithTime=withTime;
   }

   public void Dispose(){
       threadScope.Value = previous;
   }
}


class Program {
   public static void Main(params string[] args){
       new Program().Run();
   }

   public void Run(){
      log("something happend!");
      using(new LoggerScope(false)){
          log("the quick brown fox jumps over the lazy dog!");
          using(new LoggerScope(true)){
              log("nested scope!");
          }
      }
   }

   void log(string message){
      if(LoggerScope.Current!=null){
          Console.WriteLine(message);
          if(LoggerScope.Current.WithTime){
             Console.WriteLine(DateTime.Now);
          }
      }
   }

}

1

Die using-Anweisung weist .NET an, das im using-Block angegebene Objekt freizugeben, sobald es nicht mehr benötigt wird. Daher sollten Sie den Block 'using' für Klassen verwenden, die nachträglich bereinigt werden müssen, z. B. System.IO-Typen.


1

Es gibt zwei Verwendungen des usingSchlüsselworts in C # wie folgt.

  1. Als Richtlinie

    Generell verwenden wir die using Schlüsselwort, um Namespaces in CodeBehind- und Klassendateien hinzuzufügen. Anschließend werden alle Klassen, Schnittstellen und abstrakten Klassen sowie deren Methoden und Eigenschaften auf der aktuellen Seite verfügbar gemacht.

    Beispiel:

    using System.IO;
  2. Als Aussage

    Dies ist eine weitere Möglichkeit, das usingSchlüsselwort in C # zu verwenden. Es spielt eine wichtige Rolle bei der Verbesserung der Leistung in der Garbage Collection.

    Die usingAnweisung stellt sicher, dass Dispose () aufgerufen wird, auch wenn beim Erstellen von Objekten und Aufrufen von Methoden, Eigenschaften usw. eine Ausnahme auftritt. Dispose () ist eine Methode, die in der IDisposable-Schnittstelle vorhanden ist und die Implementierung einer benutzerdefinierten Garbage Collection unterstützt. Mit anderen Worten, wenn ich eine Datenbankoperation ausführe (Einfügen, Aktualisieren, Löschen), aber irgendwie eine Ausnahme auftritt, schließt hier die using-Anweisung die Verbindung automatisch. Die Methode Close () muss nicht explizit aufgerufen werden.

    Ein weiterer wichtiger Faktor ist, dass es beim Verbindungspooling hilft. Das Verbindungspooling in .NET hilft dabei, das mehrmalige Schließen einer Datenbankverbindung zu verhindern. Es sendet das Verbindungsobjekt zur zukünftigen Verwendung an einen Pool (nächster Datenbankaufruf). Wenn das nächste Mal eine Datenbankverbindung von Ihrer Anwendung aufgerufen wird, ruft der Verbindungspool die im Pool verfügbaren Objekte ab. Dies hilft, die Leistung der Anwendung zu verbessern. Wenn wir also die using-Anweisung verwenden, sendet der Controller das Objekt automatisch an den Verbindungspool. Es ist nicht erforderlich, die Methoden Close () und Dispose () explizit aufzurufen.

    Sie können das Gleiche tun wie die using-Anweisung, indem Sie den try-catch-Block verwenden und Dispose () innerhalb des finally-Blocks explizit aufrufen. Die using-Anweisung führt die Aufrufe jedoch automatisch aus, um den Code sauberer und eleganter zu gestalten. Innerhalb des using-Blocks ist das Objekt schreibgeschützt und kann nicht geändert oder neu zugewiesen werden.

    Beispiel:

    string connString = "Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;";
    
    using (SqlConnection conn = new SqlConnection(connString))
    {
          SqlCommand cmd = conn.CreateCommand();
          cmd.CommandText = "SELECT CustomerId, CompanyName FROM Customers";
          conn.Open();
          using (SqlDataReader dr = cmd.ExecuteReader())
          {
             while (dr.Read())
             Console.WriteLine("{0}\t{1}", dr.GetString(0), dr.GetString(1));
          }
    }

Im vorhergehenden Code schließe ich keine Verbindung; es wird automatisch geschlossen. Die usingAnweisung ruft conn.Close () aufgrund der usingAnweisung () automatisch auf using (SqlConnection conn = new SqlConnection(connString)und dasselbe für ein SqlDataReader-Objekt. Und auch wenn eine Ausnahme auftritt, wird die Verbindung automatisch geschlossen.

Weitere Informationen finden Sie unter Verwendung und Bedeutung der Verwendung in C # .



-1

Die Verwendung als Anweisung ruft automatisch die Dispose für das angegebene Objekt auf. Das Objekt muss die IDisposable-Schnittstelle implementieren. Es ist möglich, mehrere Objekte in einer Anweisung zu verwenden, solange sie vom gleichen Typ sind.

Die CLR konvertiert Ihren Code in MSIL. Und die using-Anweisung wird in einen Versuch übersetzt und schließlich blockiert. So wird die using-Anweisung in IL dargestellt. Eine using-Anweisung besteht aus drei Teilen: Erwerb, Verwendung und Entsorgung. Die Ressource wird zuerst erfasst, dann wird die Verwendung in einer try-Anweisung mit einer finally-Klausel eingeschlossen. Das Objekt wird dann in der finally-Klausel entsorgt.


-3

Mit Klausel wird der Bereich für die jeweilige Variable definiert. Zum Beispiel:

     Using(SqlConnection conn=new SqlConnection(ConnectionString)
            {
                Conn.Open()
            // Execute sql statements here.
           // You do not have to close the connection explicitly here as "USING" will close the connection once the object Conn becomes out of the defined scope.
            }

Dies könnte jemanden irreführen, der zum Entsorgen von Gegenständen verwendet wird. Vielleicht verwechseln Sie dies mit einem Codeblock. Wenn Sie den Umfang einer Variablen einschränken möchten, können Sie dafür einen verschachtelten Codeblock verwenden: public static void Main (params string [] args) {{// verschachtelter Codeblock}}
luiseduardohd
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.