Konsolenanwendung zur Kennwortmaskierung


201

Ich habe den folgenden Code ausprobiert ...

string pass = "";
Console.Write("Enter your password: ");
ConsoleKeyInfo key;

do
{
    key = Console.ReadKey(true);

    // Backspace Should Not Work
    if (key.Key != ConsoleKey.Backspace)
    {
        pass += key.KeyChar;
        Console.Write("*");
    }
    else
    {
        Console.Write("\b");
    }
}
// Stops Receving Keys Once Enter is Pressed
while (key.Key != ConsoleKey.Enter);

Console.WriteLine();
Console.WriteLine("The Password You entered is : " + pass);

Auf diese Weise funktioniert die Rücktaste beim Eingeben des Kennworts jedoch nicht. Irgendein Vorschlag?


11
Ich schlage vor, Sie geben nichts an die Konsole zurück, da dadurch die Länge des Kennworts offengelegt wird.
Ray Cheng

8
@ RayCheng - fair genug, aber nur sehr wenige Benutzeroberflächen (außer auf einigen Unix-Systemen) geben überhaupt nichts wieder. Für eine konsistente Benutzererfahrung mit anderen Apps und Websites ist es wahrscheinlich am besten, die * -Zeichen anzuzeigen.
Stephen Holt

4
@StephenHolt Ich bin mir ziemlich sicher, dass jede terminalbasierte Passworteingabe, auf die ich jemals gestoßen bin, nichts an das Terminal zurückgibt. Angesichts der Sicherheitsvorteile und der Tatsache, dass dies eine in der Unix-Welt bekannte Konvention ist, halte ich es für die richtige Wahl, nichts zu wiederholen, es sei denn, Sie glauben, dass Ihre Benutzerbasis mit der Verwendung von Terminals wahrscheinlich nicht vertraut ist (in diesem Fall) Es ist wahrscheinlich sowieso am besten, stattdessen eine GUI zu verwenden.
Ajedi32

Antworten:


225

Console.Write("\b \b");löscht das Sternchen vom Bildschirm, aber Sie haben keinen Code in Ihrem elseBlock, der das zuvor eingegebene Zeichen aus Ihrer passZeichenfolgenvariablen entfernt.

Hier ist der relevante Arbeitscode, der das tun sollte, was Sie benötigen:

string pass = "";
do
{
    ConsoleKeyInfo key = Console.ReadKey(true);
    // Backspace Should Not Work
    if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
    {
        pass += key.KeyChar;
        Console.Write("*");
    }
    else
    {
        if (key.Key == ConsoleKey.Backspace && pass.Length > 0)
        {
            pass = pass.Substring(0, (pass.Length - 1));
            Console.Write("\b \b");
        }
        else if(key.Key == ConsoleKey.Enter)
        {
            break;
        }
    }
} while (true);

2
Oh, ich dachte, \ b \ b bringt mich zwei Plätze zurück. Trotzdem scheint dies einwandfrei zu funktionieren.
Mohammad Nadeem

8
@Nadeem: Beachten Sie das Leerzeichen ( ' ') zwischen den Rücktastezeichen ( '\b'). "\b \b"Nimmt Sie einen Platz zurück, druckt dann ein Leerzeichen (das Sie einen Platz vorwärts bringt) und bringt Sie dann wieder zurück, sodass Sie dort landen, wo sich das gelöschte '*'Zeichen befand.
dtb

14
@Nadeem - Die ersten \bbewegt den Cursor eine Position nach hinten (jetzt unter dem letzten *Zeichen der. [space]Zeichen „druckt über“ das Sternchen, sondern bewegt sich auch der Cursor um ein Zeichen nach vorne wieder, so die letzten \bbewegt den Cursor zurück, wo der letzte *verwendet sei! (Puh - Hoffe das macht Sinn!)
CraigTP

3
if (pass.Length > 0)sollte if (key.Key == ConsoleKey.Backspace && pass.Length > 0)sonst sein, erhalten Sie nicht das letzte Zeichen des Passworts ..
MemphiZ

9
Wenn Sie nicht möchten , dass der Benutzer Schreibsteuerzeichen der Lage sein (wie F5 oder Flucht), könnten Sie ersetzen if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)mit if (!char.IsControl(key.KeyChar)).
Safron

90

Verwenden Sie dazu den System.Security.SecureString

public SecureString GetPassword()
{
    var pwd = new SecureString();
    while (true)
    {
        ConsoleKeyInfo i = Console.ReadKey(true);
        if (i.Key == ConsoleKey.Enter)
        {
            break;
        }
        else if (i.Key == ConsoleKey.Backspace)
        {
            if (pwd.Length > 0)
            {
                pwd.RemoveAt(pwd.Length - 1);
                Console.Write("\b \b");
            }
        }
        else if (i.KeyChar != '\u0000' ) // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc
        {
            pwd.AppendChar(i.KeyChar);
            Console.Write("*");
        }
    }
    return pwd;
}

Dies bringt mich nur zwei Plätze zurück. Was ich aber brauche, ist, dass das letzte Zeichen gelöscht wird, wenn ich die Rücktaste drücke. Genau wie die ursprüngliche Funktion der Rücktaste.
Mohammad Nadeem

1
Ich musste mich if( pwd.Length > 0)in die erste else-Anweisung einnisten , um zu verhindern, dass Leute die Frage löschen :)
Dead.Rabit

1
Ähnlich wie Safrons Kommentar zur derzeit akzeptierten Antwort elsewürde die letzte Klausel von einem Test if (!char.IsControl(i.KeyChar))(oder zumindest if (i.KeyChar != '\u0000')) profitieren .
Peter Taylor

1
Wie kann ich dieses Passwort in eine Zeichenfolge umwandeln?
Joseph Kreifels II


47

Komplettlösung, Vanille C # .net 3.5+

Ausschneiden Einfügen :)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace ConsoleReadPasswords
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Write("Password:");

                string password = Orb.App.Console.ReadPassword();

                Console.WriteLine("Sorry - I just can't keep a secret!");
                Console.WriteLine("Your password was:\n<Password>{0}</Password>", password);

                Console.ReadLine();
            }
        }
    }

    namespace Orb.App
    {
        /// <summary>
        /// Adds some nice help to the console. Static extension methods don't exist (probably for a good reason) so the next best thing is congruent naming.
        /// </summary>
        static public class Console
        {
            /// <summary>
            /// Like System.Console.ReadLine(), only with a mask.
            /// </summary>
            /// <param name="mask">a <c>char</c> representing your choice of console mask</param>
            /// <returns>the string the user typed in </returns>
            public static string ReadPassword(char mask)
            {
                const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127;
                int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const

                var pass = new Stack<char>();
                char chr = (char)0;

                while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER)
                {
                    if (chr == BACKSP)
                    {
                        if (pass.Count > 0)
                        {
                            System.Console.Write("\b \b");
                            pass.Pop();
                        }
                    }
                    else if (chr == CTRLBACKSP)
                    {
                        while (pass.Count > 0)
                        {
                            System.Console.Write("\b \b");
                            pass.Pop();
                        }
                    }
                    else if (FILTERED.Count(x => chr == x) > 0) { }
                    else
                    {
                        pass.Push((char)chr);
                        System.Console.Write(mask);
                    }
                }

                System.Console.WriteLine();

                return new string(pass.Reverse().ToArray());
            }

            /// <summary>
            /// Like System.Console.ReadLine(), only with a mask.
            /// </summary>
            /// <returns>the string the user typed in </returns>
            public static string ReadPassword()
            {
                return Orb.App.Console.ReadPassword('*');
            }
        }
    }

3
Es kann immer schwieriger sein :) Funktioniert nicht unter Mac / Linux, da die Newline nicht erkannt wird. Environment.NewLine enthält die Zeichenfolge für eine neue Zeile. Also habe ich dies geändert in: while (! Environment.NewLine.Contains (chr = System.Console.ReadKey (true) .KeyChar))
Hugo Logmans

18

Nehmen Sie die oberste Antwort sowie die Vorschläge aus den Kommentaren und ändern Sie sie, um SecureString anstelle von String zu verwenden. Testen Sie alle Steuerschlüssel, und geben Sie keine Fehler ein oder schreiben Sie ein zusätzliches "*" auf den Bildschirm, wenn die Kennwortlänge 0 ist. Meine Lösung ist:

public static SecureString getPasswordFromConsole(String displayMessage) {
    SecureString pass = new SecureString();
    Console.Write(displayMessage);
    ConsoleKeyInfo key;

    do {
        key = Console.ReadKey(true);

        // Backspace Should Not Work
        if (!char.IsControl(key.KeyChar)) {
            pass.AppendChar(key.KeyChar);
            Console.Write("*");
        } else {
            if (key.Key == ConsoleKey.Backspace && pass.Length > 0) {
                pass.RemoveAt(pass.Length - 1);
                Console.Write("\b \b");
            }
        }
    }
    // Stops Receving Keys Once Enter is Pressed
    while (key.Key != ConsoleKey.Enter);
    return pass;
}

15

Meins ignoriert Steuerzeichen und behandelt Zeilenumbrüche:

public static string ReadLineMasked(char mask = '*')
{
    var sb = new StringBuilder();
    ConsoleKeyInfo keyInfo;
    while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter)
    {
        if (!char.IsControl(keyInfo.KeyChar))
        {
            sb.Append(keyInfo.KeyChar);
            Console.Write(mask);
        }
        else if (keyInfo.Key == ConsoleKey.Backspace && sb.Length > 0)
        {
            sb.Remove(sb.Length - 1, 1);

            if (Console.CursorLeft == 0)
            {
                Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
                Console.Write(' ');
                Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
            }
            else Console.Write("\b \b");
        }
    }
    Console.WriteLine();
    return sb.ToString();
}

Funktioniert perfekt. Möglicherweise müssen Sie Code hinzufügen, damit das DELETE Zeichen den gesamten eingegebenen Text löscht. Die Tastenfolge lautet CTRL + BACKSPACEund der Zeichencode lautet 0x7f.
Alex Essilfie

9

Das Lesen der Konsoleneingabe ist schwierig. Sie müssen spezielle Tasten wie Strg, Alt, auch Cursortasten und Rücktaste / Löschen verwenden. Bei einigen Tastaturlayouts wie der schwedischen Strg-Taste wird sogar die Eingabe von Tasten benötigt, die direkt auf der US-Tastatur vorhanden sind. Ich glaube, dass der Versuch, dies mit dem "Low-Level" zu handhaben,Console.ReadKey(true) nur sehr schwierig ist. Daher ist es am einfachsten und robustesten, das "Konsoleneingabe-Echo" während der Eingabe des Passworts mit etwas WINAPI zu deaktivieren.

Das folgende Beispiel basiert auf der Antwort auf die Frage " Passwort aus std :: cin lesen" .

    private enum StdHandle
    {
        Input = -10,
        Output = -11,
        Error = -12,
    }

    private enum ConsoleMode
    {
        ENABLE_ECHO_INPUT = 4
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(StdHandle nStdHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out int lpMode);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetConsoleMode(IntPtr hConsoleHandle, int dwMode);

    public static string ReadPassword()
    {
        IntPtr stdInputHandle = GetStdHandle(StdHandle.Input);
        if (stdInputHandle == IntPtr.Zero)
        {
            throw new InvalidOperationException("No console input");
        }

        int previousConsoleMode;
        if (!GetConsoleMode(stdInputHandle , out previousConsoleMode))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not get console mode.");
        }

        // disable console input echo
        if (!SetConsoleMode(stdInputHandle , previousConsoleMode & ~(int)ConsoleMode.ENABLE_ECHO_INPUT))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not disable console input echo.");
        }

        // just read the password using standard Console.ReadLine()
        string password = Console.ReadLine();

        // reset console mode to previous
        if (!SetConsoleMode(stdInputHandle , previousConsoleMode))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not reset console mode.");
        }

        return password;
    }

9

Dadurch wird das Kennwort mit einem roten Quadrat maskiert und nach Eingabe des Kennworts auf die ursprünglichen Farben zurückgesetzt.

Es hindert den Benutzer nicht daran, das Kennwort durch Kopieren / Einfügen abzurufen. Wenn es jedoch nur darum geht, jemanden davon abzuhalten, über die Schulter zu schauen, ist dies eine gute schnelle Lösung.

Console.Write("Password ");
ConsoleColor origBG = Console.BackgroundColor; // Store original values
ConsoleColor origFG = Console.ForegroundColor;

Console.BackgroundColor = ConsoleColor.Red; // Set the block colour (could be anything)
Console.ForegroundColor = ConsoleColor.Red;

string Password = Console.ReadLine(); // read the password

Console.BackgroundColor= origBG; // revert back to original
Console.ForegroundColor= origFG;

Das einzige Problem ist, dass der Hintergrund, in dem Sie Zeichen hatten, rot bleibt, wenn Sie die Rücktaste drücken. Ich würde lieber ForegroundColor als origBG festlegen, um eine Passworteingabe im Linux-Stil zu erhalten.
Mababin

1
Sie können dies auch tun Console.CursorVisible=falseund danach auf den vorherigen Wert zurücksetzen. Dies würde verhindern, dass jemand die Kennwortlänge erreicht.
Mababin

6

Ich habe einen Fehler in Shermys Vanilla C # 3.5 .NET-Lösung gefunden, der ansonsten einen Reiz hat. Ich habe hier auch Damian Leszczyński - Vashs SecureString-Idee aufgenommen, aber Sie können eine gewöhnliche Saite verwenden, wenn Sie es vorziehen.

DER BUG: Wenn Sie während der Passwortabfrage die Rücktaste drücken und die aktuelle Länge des Passworts 0 ist, wird ein Sternchen falsch in die Passwortmaske eingefügt. Um diesen Fehler zu beheben, ändern Sie die folgende Methode.

    public static string ReadPassword(char mask)
    {
        const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127;
        int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const


        SecureString securePass = new SecureString();

        char chr = (char)0;

        while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER)
        {
            if (((chr == BACKSP) || (chr == CTRLBACKSP)) 
                && (securePass.Length > 0))
            {
                System.Console.Write("\b \b");
                securePass.RemoveAt(securePass.Length - 1);

            }
            // Don't append * when length is 0 and backspace is selected
            else if (((chr == BACKSP) || (chr == CTRLBACKSP)) && (securePass.Length == 0))
            {
            }

            // Don't append when a filtered char is detected
            else if (FILTERED.Count(x => chr == x) > 0)
            {
            }

            // Append and write * mask
            else
            {
                securePass.AppendChar(chr);
                System.Console.Write(mask);
            }
        }

        System.Console.WriteLine();
        IntPtr ptr = new IntPtr();
        ptr = Marshal.SecureStringToBSTR(securePass);
        string plainPass = Marshal.PtrToStringBSTR(ptr);
        Marshal.ZeroFreeBSTR(ptr);
        return plainPass;
    }

4

Hier ist eine Version, die Unterstützung für den EscapeSchlüssel hinzufügt (der eine nullZeichenfolge zurückgibt ).

public static string ReadPassword()
{
    string password = "";
    while (true)
    {
        ConsoleKeyInfo key = Console.ReadKey(true);
        switch (key.Key)
        {
            case ConsoleKey.Escape:
                return null;
            case ConsoleKey.Enter:
                return password;
            case ConsoleKey.Backspace:
                if (password.Length > 0) 
                {
                    password = password.Substring(0, (password.Length - 1));
                    Console.Write("\b \b");
                }
                break;
            default:
                password += key.KeyChar;
                Console.Write("*");
                break;
        }
    }
}

2

(Mein) Nuget-Paket, um dies zu tun, basierend auf der Top-Antwort:

install-package PanoramicData.ConsoleExtensions

Verwendung:

using PanoramicData.ConsoleExtensions;

...

Console.Write("Password: ");
var password = ConsolePlus.ReadPassword();
Console.WriteLine();

Projekt-URL: https://github.com/panoramicdata/PanoramicData.ConsoleExtensions

Pull-Anfragen willkommen.


Ich habe das für ein kleines Projekt verwendet. Arbeitete wie erwartet. Vielen Dank
gmalenko

1

Sie können Ihre Schlüssel an eine akkumulierte verknüpfte Liste anhängen.

Wenn eine Rücktaste empfangen wird, entfernen Sie den letzten Schlüssel aus der Liste.

Wenn Sie die Eingabetaste erhalten, reduzieren Sie Ihre Liste zu einer Zeichenfolge und erledigen Sie den Rest Ihrer Arbeit.


Klingt erreichbar, aber wie entferne ich das letzte Zeichen von der Anzeige.
Mohammad Nadeem

1

Ich habe einige Änderungen für die Rücktaste vorgenommen

        string pass = "";
        Console.Write("Enter your password: ");
        ConsoleKeyInfo key;

        do
        {
            key = Console.ReadKey(true);

            // Backspace Should Not Work
            if (key.Key != ConsoleKey.Backspace)
            {
                pass += key.KeyChar;
                Console.Write("*");
            }
            else
            {
                pass = pass.Remove(pass.Length - 1);
                Console.Write("\b \b");
            }
        }
        // Stops Receving Keys Once Enter is Pressed
        while (key.Key != ConsoleKey.Enter);

        Console.WriteLine();
        Console.WriteLine("The Password You entered is : " + pass);

1

Hier ist meine einfache Version. Löschen Sie jedes Mal, wenn Sie eine Taste drücken, alle Elemente von der Konsole und zeichnen Sie so viele '*', wie die Länge der Kennwortzeichenfolge beträgt.

int chr = 0;
string pass = "";
const int ENTER = 13;
const int BS = 8;

do
{
   chr = Console.ReadKey().KeyChar;
   Console.Clear(); //imediately clear the char you printed

   //if the char is not 'return' or 'backspace' add it to pass string
   if (chr != ENTER && chr != BS) pass += (char)chr;

   //if you hit backspace remove last char from pass string
   if (chr == BS) pass = pass.Remove(pass.Length-1, 1);

   for (int i = 0; i < pass.Length; i++)
   {
      Console.Write('*');
   }
} 
 while (chr != ENTER);

Console.Write("\n");
Console.Write(pass);

Console.Read(); //just to see the pass

0

Ich habe Ronnies Version aktualisiert, nachdem ich viel zu viel Zeit verbracht habe damit verbracht habe, ein Passwort einzugeben, nur um herauszufinden, dass ich mein CAPS LOCK aktiviert hatte!

Bei dieser Version _CapsLockMessage"schwebt" die Nachricht am Ende des Eingabebereichs und wird rot angezeigt.

Diese Version benötigt etwas mehr Code und erfordert eine Abfrageschleife. Auf meinem Computer ist die CPU-Auslastung zwischen 3% und 4%, aber man kann immer einen kleinen Sleep () -Wert hinzufügen, um die CPU-Auslastung bei Bedarf zu verringern.

    private const string _CapsLockMessage = " CAPS LOCK";

    /// <summary>
    /// Like System.Console.ReadLine(), only with a mask.
    /// </summary>
    /// <param name="mask">a <c>char</c> representing your choice of console mask</param>
    /// <returns>the string the user typed in</returns>
    public static string ReadLineMasked(char mask = '*')
    {
        // Taken from http://stackoverflow.com/a/19770778/486660
        var consoleLine = new StringBuilder();
        ConsoleKeyInfo keyInfo;
        bool isDone;
        bool isAlreadyLocked;
        bool isCapsLockOn;
        int cursorLeft;
        int cursorTop;
        ConsoleColor originalForegroundColor;

        isDone = false;
        isAlreadyLocked = Console.CapsLock;

        while (isDone == false)
        {
            isCapsLockOn = Console.CapsLock;
            if (isCapsLockOn != isAlreadyLocked)
            {
                if (isCapsLockOn)
                {
                    cursorLeft = Console.CursorLeft;
                    cursorTop = Console.CursorTop;
                    originalForegroundColor = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("{0}", _CapsLockMessage);
                    Console.SetCursorPosition(cursorLeft, cursorTop);
                    Console.ForegroundColor = originalForegroundColor;
                }
                else
                {
                    cursorLeft = Console.CursorLeft;
                    cursorTop = Console.CursorTop;
                    Console.Write("{0}", string.Empty.PadRight(_CapsLockMessage.Length));
                    Console.SetCursorPosition(cursorLeft, cursorTop);
                }
                isAlreadyLocked = isCapsLockOn;
            }

            if (Console.KeyAvailable)
            {
                keyInfo = Console.ReadKey(intercept: true);

                if (keyInfo.Key == ConsoleKey.Enter)
                {
                    isDone = true;
                    continue;
                }

                if (!char.IsControl(keyInfo.KeyChar))
                {
                    consoleLine.Append(keyInfo.KeyChar);
                    Console.Write(mask);
                }
                else if (keyInfo.Key == ConsoleKey.Backspace && consoleLine.Length > 0)
                {
                    consoleLine.Remove(consoleLine.Length - 1, 1);

                    if (Console.CursorLeft == 0)
                    {
                        Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
                        Console.Write(' ');
                        Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
                    }
                    else
                    {
                        Console.Write("\b \b");
                    }
                }

                if (isCapsLockOn)
                {
                    cursorLeft = Console.CursorLeft;
                    cursorTop = Console.CursorTop;
                    originalForegroundColor = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.Write("{0}", _CapsLockMessage);
                    Console.CursorLeft = cursorLeft;
                    Console.CursorTop = cursorTop;
                    Console.ForegroundColor = originalForegroundColor;
                }
            }
        }

        Console.WriteLine();

        return consoleLine.ToString();
    }

-1

Wenn ich das richtig verstehe, versuchen Sie, die Rücktaste dazu zu bringen, sowohl das sichtbare * Zeichen auf dem Bildschirm als auch das zwischengespeicherte Zeichen in Ihrer Passvariablen zu löschen?

Wenn ja, ändern Sie einfach Ihren else-Block in diesen:

            else
            {
                Console.Write("\b");
                pass = pass.Remove(pass.Length -1);
            }

1
Dies funktioniert einwandfrei, außer dass das Löschen des Zeichens durch die Rücktaste nicht angezeigt wird.
Mohammad Nadeem

-2
 string pass = "";
 Console.WriteLine("Enter your password: ");
 ConsoleKeyInfo key;

 do {
  key = Console.ReadKey(true);

  if (key.Key != ConsoleKey.Backspace) {
   pass += key.KeyChar;
   Console.Write("*");
  } else {
   Console.Write("\b \b");
   char[] pas = pass.ToCharArray();
   string temp = "";
   for (int i = 0; i < pass.Length - 1; i++) {
    temp += pas[i];
   }
   pass = temp;
  }
 }
 // Stops Receving Keys Once Enter is Pressed
 while (key.Key != ConsoleKey.Enter);

 Console.WriteLine();
 Console.WriteLine("The Password You entered is : " + pass);

1
Diese Antwort fügt nichts hinzu, was über die vorhandenen Antworten hinausgeht. Darüber hinaus sollten gute Antworten in der Regel den Code erklären und nicht nur Code in das Antwortfeld einfügen. Bitte lesen Sie, wie zu
antworten
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.