Как определить, является ли строка допустимым именем переменной? - PullRequest
23 голосов
/ 02 декабря 2009

Я ищу быстрый способ (в C #) определить, является ли строка допустимым именем переменной. Моя первая интуиция - это создать какое-то регулярное выражение, но мне интересно, есть ли лучший способ сделать это. Как, может быть, какой-то секретный метод, скрытый где-то глубоко под названием IsThisAValidVariableName (имя строки), или какой-то другой хитрый способ сделать это, который не подвержен ошибкам, которые могут возникнуть из-за отсутствия мастерства регулярных выражений.

Ответы [ 5 ]

46 голосов
/ 02 декабря 2009

Попробуйте это:

// using System.CodeDom.Compiler;
CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
if (provider.IsValidIdentifier (YOUR_VARIABLE_NAME)) {
      // Valid
} else {
      // Not valid
}
3 голосов
/ 20 июля 2017
  public static bool IsIdentifier(string text)
  {
     if (string.IsNullOrEmpty(text))
        return false;
     if (!char.IsLetter(text[0]) && text[0] != '_')
        return false;
     for (int ix = 1; ix < text.Length; ++ix)
        if (!char.IsLetterOrDigit(text[ix]) && text[ix] != '_')
           return false;
     return true;
  }
2 голосов
/ 11 марта 2017

Вокруг символа @ есть пара особых случаев, которые легко забыть проверить, а именно: '@' сам по себе не является допустимым идентификатором и не является "@1foo". Чтобы поймать их, вы можете сначала проверить, является ли строка ключевым словом, затем удалить @ из начала строки, а затем проверить, является ли то, что осталось, допустимым идентификатором (запрещающий @ символов).

Здесь я объединил это с методом для анализа escape-последовательностей Unicode в идентификаторах и, надеюсь, завершил проверку символов Unicode в C # (5.0). Чтобы использовать его, сначала вызовите TryParseRawIdentifier() для обработки ключевых слов, escape-последовательностей, символов форматирования (которые удаляются) и дословных идентификаторов. Затем передайте результат в IsValidParsedIdentifier(), чтобы проверить правильность первого и последующих символов. Обратите внимание, что строки, возвращаемые из TryParseRawIdentifier(), равны, если и только если идентификаторы считаются идентичными в C #.

public static class CSharpIdentifiers
{
    private static HashSet<string> _keywords = new HashSet<string> {
        "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked",
        "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else",
        "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for",
        "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock",
        "long", "namespace", "new", "null", "object", "operator", "out", "override", "params",
        "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed",
        "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw",
        "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using",
        "virtual", "void", "volatile", "while"
    };

    public static IReadOnlyCollection<string> Keywords { get { return _keywords; } }


    public static bool TryParseRawIdentifier(string str, out string parsed)
    {
        if (string.IsNullOrEmpty(str) || _keywords.Contains(str)) { parsed = null; return false; }

        StringBuilder sb = new StringBuilder(str.Length);

        int verbatimCharWidth = str[0] == '@' ? 1 : 0;

        for (int i = verbatimCharWidth; i < str.Length; ) //Manual increment
        {
            char c = str[i];

            if (c == '\\')
            {
                char next = str[i + 1];

                int charCodeLength;
                if (next == 'u') charCodeLength = 4;
                else if (next == 'U') charCodeLength = 8;
                else { parsed = null; return false; }
                //No need to check for escaped backslashes or special sequences like \n,
                //as they not valid identifier characters

                int charCode;
                if (!TryParseHex(str.Substring(i + 2, charCodeLength), out charCode)) { parsed = null; return false; }

                sb.Append(char.ConvertFromUtf32(charCodeLength)); //Handle characters above 2^16 by converting them to a surrogate pair
                i += 2 + charCodeLength;
            }
            else if (char.GetUnicodeCategory(str, i) == UnicodeCategory.Format)
            {
                //Use (string, index) in order to handle surrogate pairs
                //Skip this character
                if (char.IsSurrogatePair(str, i)) i += 2;
                else i += 1;
            }
            else
            {
                sb.Append(c);
                i++;
            }
        }

        parsed = sb.ToString();
        return true;
    }

    private static bool TryParseHex(string str, out int result)
    {
        return int.TryParse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out result);
        //NumberStyles.AllowHexSpecifier forces all characters to be hex digits
    }

    public static bool IsValidParsedIdentifier(string str)
    {
        if (string.IsNullOrEmpty(str)) return false;

        if (!IsValidParsedIdentifierStart(str, 0)) return false;

        int firstCharWidth = char.IsSurrogatePair(str, 0) ? 2 : 1;

        for (int i = firstCharWidth; i < str.Length; ) //Manual increment
        {
            if (!IsValidParsedIdentifierPart(str, i)) return false;
            if (char.IsSurrogatePair(str, i)) i += 2;
            else i += 1;
        }

        return true;
    }

    //(String, index) pairs are used instead of chars in order to support surrogate pairs
    //(Unicode code-points above 2^16 represented using two 16-bit characters)

    public static bool IsValidParsedIdentifierStart(string s, int index)
    {
        return s[index] == '_' || char.IsLetter(s, index) || char.GetUnicodeCategory(s, index) == UnicodeCategory.LetterNumber;
    }

    public static bool IsValidParsedIdentifierPart(string s, int index)
    {
        if (s[index] == '_' || (s[index] >= '0' && s[index] <= '9') || char.IsLetter(s, index)) return true;

        switch (char.GetUnicodeCategory(s, index))
        {
            case UnicodeCategory.LetterNumber: //Eg. Special Roman numeral characters (not covered by IsLetter())
            case UnicodeCategory.DecimalDigitNumber: //Includes decimal digits in other cultures
            case UnicodeCategory.ConnectorPunctuation:
            case UnicodeCategory.NonSpacingMark:
            case UnicodeCategory.SpacingCombiningMark:
            //UnicodeCategory.Format handled in TryParseRawIdentifier()
                return true;
            default:
                return false;
        }
    }
}
1 голос
/ 02 декабря 2009

Более длинный путь, плюс гораздо более медленный, состоит в том, чтобы использовать отражение, чтобы перебрать членов класса / пространства имен и сравнить, проверяя, является ли отраженный член **. ToString () ** таким же, как строковый ввод, для этого требуется предварительно загрузить сборку.

Другой способ сделать это (гораздо более длинный путь, который преодолевает использование регулярных выражений, используя уже доступный сканер / синтаксический анализатор Antlr), граничит с синтаксическим анализом / лексированием кода C #, а затем сканирует имена членов (т.е. переменные) и сравнивая со строкой, используемой в качестве входных данных, например, введите строку с именем 'fooBar', затем укажите источник (например, код сборки или C #) и отсканируйте его, проанализировав специально для поиска объявлений элементов, таких как, например,

private int fooBar;

Да, это сложно, но глубокое понимание возникнет, когда вы поймете, что делают авторы компилятора, и улучшит ваши знания языка C # до уровня, когда вы достаточно близко познакомитесь с синтаксисом и его особенностями.

0 голосов
/ 20 апреля 2019

В WPF это можно использовать для проверки, является ли строка допустимым именем переменной. Но он не распознает зарезервированные строки, такие как public.

// works only in WPF!
public static bool CheckIfStringIsValidVarName(string stringToCheck)
{
    if (string.IsNullOrWhiteSpace(stringToCheck))
        return false;

    TextBox textBox = new TextBox();

    try
    {
        // stringToCheck == ""; // !!! does NOT throw !!!
        // stringToCheck == "Name$"; // throws
        // stringToCheck == "0"; // throws
        // stringToCheck == "name with blank"; // throws
        // stringToCheck == "public"; // does NOT throw
        // stringToCheck == "ValidName";

        textBox.Name = stringToCheck;
    }
    catch (ArgumentException ex)
    {
        return false;
    }

    return true;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...