Проверить, является ли строка guid без исключения?


180

Я хочу попытаться преобразовать строку в Guid, но я не хочу полагаться на перехват исключений (

  • по причинам производительности - исключения дорогие
  • по соображениям удобства использования - появляется отладчик
  • по конструктивным причинам - ожидаемое не исключение

Другими словами код:

public static Boolean TryStrToGuid(String s, out Guid value)
{
    try
    {
        value = new Guid(s);
        return true;
    }
    catch (FormatException)
    {
        value = Guid.Empty;
        return false;
    }
}

не подходит.

Я бы попробовал использовать RegEx, но так как guid может быть заключен в скобки, заключен в скобки, ни один не обернут, это затрудняет работу.

Кроме того, я думал, что некоторые значения Guid являются недействительными (?)


Обновление 1

У ChristianK была хорошая идея ловить только FormatException, а не все. Изменен пример кода вопроса, чтобы включить предложение.


Обновление 2

Зачем беспокоиться о брошенных исключениях? Неужели я так часто жду неверных идентификаторов GUID?

Ответ - да . Именно поэтому я использую TryStrToGuid - я имею ожидая плохие данные.

Пример 1 Расширения пространства имен можно указать, добавив GUID к имени папки . Возможно, я разбираю имена папок, проверяя, есть ли текст после финала . это GUID.

c:\Program Files
c:\Program Files.old
c:\Users
c:\Users.old
c:\UserManager.{CE7F5AA5-6832-43FE-BAE1-80D14CD8F666}
c:\Windows
c:\Windows.old

Пример 2 Возможно, я использую интенсивно используемый веб-сервер, который хочет проверить достоверность некоторых опубликованных данных. Я не хочу, чтобы недопустимые данные связывали ресурсы на 2-3 порядка выше, чем нужно.

Пример 3 Я могу проанализировать поисковое выражение, введенное пользователем.

введите описание изображения здесь

Если они вводят GUID, я хочу специально обработать их (например, специально искать этот объект или выделить и отформатировать этот конкретный поисковый термин в тексте ответа).


Обновление 3 - Тесты производительности

Тест на преобразование 10 000 хороших гидов и 10 000 плохих гидов.

Catch FormatException:
   10,000 good:     63,668 ticks
   10,000 bad:   6,435,609 ticks

Regex Pre-Screen with try-catch:
   10,000 good:    637,633 ticks
   10,000 bad:     717,894 ticks

COM Interop CLSIDFromString
   10,000 good:    126,120 ticks
   10,000 bad:      23,134 ticks

PS Мне не нужно было обосновывать вопрос.


7
Почему в мире это вики сообщества?
Джефф

36
Ты прав; Вы не должны обосновывать вопрос. Тем не менее, я с интересом читаю обоснование (так как оно очень похоже на то, почему я здесь читаю это). Итак, спасибо за большое оправдание.
мт

2
@Jeff, вероятно, потому что ОП редактировал его более 10 раз - см. Мета в вики сообщества
Marijn

3
Пожалуйста, продолжайте искать на этой странице решения для Guid.TryParse или Guid.TryParseExact. С .NET 4.0 + вышеприведенное решение не самое элегантное
dplante

1
@dplante Когда я изначально задавал вопрос в 2008 году, не было 4.0. Вот почему вопрос и принятый ответ таковы.
Ян Бойд

Ответы:


107

Тесты производительности

Catch exception:
   10,000 good:    63,668 ticks
   10,000 bad:  6,435,609 ticks

Regex Pre-Screen:
   10,000 good:   637,633 ticks
   10,000 bad:    717,894 ticks

COM Interop CLSIDFromString
   10,000 good:   126,120 ticks
   10,000 bad:     23,134 ticks

COM Intertop (Самый быстрый) Ответ:

/// <summary>
/// Attempts to convert a string to a guid.
/// </summary>
/// <param name="s">The string to try to convert</param>
/// <param name="value">Upon return will contain the Guid</param>
/// <returns>Returns true if successful, otherwise false</returns>
public static Boolean TryStrToGuid(String s, out Guid value)
{
   //ClsidFromString returns the empty guid for null strings   
   if ((s == null) || (s == ""))   
   {      
      value = Guid.Empty;      
      return false;   
   }

   int hresult = PInvoke.ObjBase.CLSIDFromString(s, out value);
   if (hresult >= 0)
   {
      return true;
   }
   else
   {
      value = Guid.Empty;
      return false;
   }
}


namespace PInvoke
{
    class ObjBase
    {
        /// <summary>
        /// This function converts a string generated by the StringFromCLSID function back into the original class identifier.
        /// </summary>
        /// <param name="sz">String that represents the class identifier</param>
        /// <param name="clsid">On return will contain the class identifier</param>
        /// <returns>
        /// Positive or zero if class identifier was obtained successfully
        /// Negative if the call failed
        /// </returns>
        [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = true)]
        public static extern int CLSIDFromString(string sz, out Guid clsid);
    }
}

Итог: если вам нужно проверить, является ли строка guid, и вы заботитесь о производительности, используйте COM Interop.

Если вам нужно преобразовать guid в представлении String в Guid, используйте

new Guid(someString);

8
Вы запускали их с включенным или выключенным отладчиком? Производительность генерации исключений улучшена в несколько раз без подключения отладчика.
Даниэль Т.

Спасибо. Я собирался задать этот вопрос сам. Рад, что нашел ваш ответ.
Дэвид

Я создал новый файл PInvoke.cs с фрагментом кода PInvoke пространства имен сверху, но не могу заставить код работать. Когда я отлаживаю, я вижу, что результат CLSIDFromString ВСЕГДА отрицательный. Я попытался изменить вызывающую линию на: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); но это все еще всегда отрицательно. Что я делаю не так?
JALLRED

88

Как только .net 4.0 станет доступен, вы можете использовать Guid.TryParse().


8
Еще более быстрый способ - использование метода Guid.TryParseExact ().

4
Если разбор строк Guid - самая медленная часть вашего приложения, то вы благословлены.
Нет возврата

65

Вам это не понравится, но с чего вы взяли, что поймать исключение будет медленнее?

Сколько неудачных попыток разобрать GUID вы ожидаете по сравнению с успешными?

Мой совет - используйте только что созданную функцию и профилируйте свой код. Если вы обнаружите, что эта функция действительно горячая точка, то исправьте ее, но не раньше.


2
Хороший ответ, преждевременная оптимизация - корень всего зла.
Кев

33
Это плохая форма, чтобы полагаться на исключения, которые не являются исключительными. Это плохая привычка, в которую я бы не хотел, чтобы кто-то впал. И я особенно не хотел бы делать это в рутине библиотеки, где люди будут полагать, что это работает и хорошо.
Ян Бойд

Аноним, в исходном вопросе вы указали производительность как причину, по которой вы хотели избежать исключений. Если это не так, то, возможно, вам следует настроить свой вопрос.
AnthonyWJones

6
Исключение следует использовать в ИСКЛЮЧИТЕЛЬНОМ случае: не управляется разработчиком. Я противник «исключений» Microsoft в способе управления ошибками. Оборонительные правила программирования. Пожалуйста, разработчики фреймворка Microsoft, рассмотрите возможность добавления TryParse в класс Guid.
Моисей

14
в ответ на мой собственный комментарий => Guid.TryParse был добавлен в framework 4.0 --- msdn.microsoft.com/en-us/library/… --- thxs MS для такой быстрой реакции;)
Mose

39

В .NET 4.0 вы можете написать следующее:

public static bool IsValidGuid(string str)
{
    Guid guid;
    return Guid.TryParse(str, out guid);
}

3
Это действительно должен быть один из лучших ответов.
Том Линт

21

Я бы хотя бы переписал это так:

try
{
  value = new Guid(s);
  return true;
}
catch (FormatException)
{
  value = Guid.Empty;
  return false;
}

Вы не хотите произносить «недопустимый GUID» в SEHException, ThreadAbortException или других фатальных или не связанных вещах.

Обновление : Начиная с .NET 4.0, для Guid доступен новый набор методов:

Действительно, их следует использовать (хотя бы потому, что они не «наивно» реализованы с использованием try-catch внутри).


13

Взаимодействие медленнее, чем просто ловить исключение:

На счастливом пути с 10 000 гидов:

Exception:    26ms
Interop:   1,201ms

На несчастном пути:

Exception: 1,150ms
  Interop: 1,201ms

Это более последовательно, но также постоянно медленнее. Мне кажется, вам лучше настроить свой отладчик так, чтобы он работал только на необработанных исключениях.


«ваш отладчик, чтобы разбить только на необработанные исключения» Не вариант.
Ян Бойд

1
@Ian Boyd - Если вы используете какой - либо из изданий VS (включая Express), то есть вариант. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Марк Брэкетт

1
Я имел в виду, что это не осуществимый вариант. Мол, «Отказ не вариант». Это является одним из вариантов, но один , что я не буду использовать.
Ян Бойд

9

Ну, вот вам и регулярное выражение ...

^[A-Fa-f0-9]{32}$|^({|\\()?[A-Fa-f0-9]{8}-([A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}(}|\\))?$|^({)?[0xA-Fa-f0-9]{3,10}(, {0,1}[0xA-Fa-f0-9]{3,6}){2}, {0,1}({)([0xA-Fa-f0-9]{3,4}, {0,1}){7}[0xA-Fa-f0-9]{3,4}(}})$

Но это только для начала. Вам также необходимо убедиться, что различные части, такие как дата / время, находятся в допустимых пределах. Я не могу представить, что это происходит быстрее, чем метод try / catch, который вы уже описали. Надеюсь, вы не получите столько недействительных идентификаторов GUID, чтобы гарантировать такой тип проверки!


Гм, идентификаторы IIRC, генерируемые из метки времени, обычно считаются плохой идеей, а другой тип (тип 4) совершенно случайный
BCS

5

по соображениям удобства использования - появляется отладчик

Если вы собираетесь использовать подход try / catch, вы можете добавить атрибут [System.Diagnostics.DebuggerHidden], чтобы убедиться, что отладчик не ломается, даже если вы установили его как разрыв на бросок.


4

Несмотря на то , что это правда , что использование ошибок является более дорогим, большинство людей считают , что большинство их GUIDs будет компьютером так TRY-CATCHне слишком дорого , так как это создает только стоимость на CATCH. Вы можете доказать это сами с помощью простого теста из двух (публичный пользователь, без пароля).

Ну вот:

using System.Text.RegularExpressions;


 /// <summary>
  /// Validate that a string is a valid GUID
  /// </summary>
  /// <param name="GUIDCheck"></param>
  /// <returns></returns>
  private bool IsValidGUID(string GUIDCheck)
  {
   if (!string.IsNullOrEmpty(GUIDCheck))
   {
    return new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$").IsMatch(GUIDCheck);
   }
   return false;
  }

4

У меня была похожая ситуация, и я заметил, что почти никогда не было неверной строки длиной 36 символов. Поэтому, основываясь на этом факте, я немного изменил ваш код, чтобы повысить производительность, сохранив при этом простоту.

public static Boolean TryStrToGuid(String s, out Guid value)
{

     // this is before the overhead of setting up the try/catch block.
     if(value == null || value.Length != 36)
     {  
        value = Guid.Empty;
        return false;
     }

    try
    {
        value = new Guid(s);
        return true;
    }
    catch (FormatException)
    {
        value = Guid.Empty;
        return false;
    }
}

1
Guid принимает больше, чем просто пунктирную строку в своем ctor. У GUID могут быть окружающие фигурные скобки с тире или без штрихов или скобок. Этот код будет генерировать ложные отрицания при использовании этих альтернативных, но также совершенно корректных строковых форм.
Крис Чарабарук

1
Для продолжения допустимыми длинами для GUID в форме строки являются 32, 36 и 38 - чисто шестнадцатеричные, пунктирные и фигурные скобки, соответственно.
Крис Чарабарук

1
@Chris, ваша точка зрения верна, но идея @JBrooks здравого смысла, проверяющая предполагаемый GUID перед тем, как переходить в try / catch, имеет смысл, особенно если подозрительный ввод является распространенным. Может быть что-то вроде if (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; вернуть false;}
bw

1
В самом деле, это было бы лучше, хотя я бы держал диапазон более узким, 32..38, а не 30..40.
Крис Чарабарук

2

Насколько я знаю, в mscrolib нет ничего похожего на Guid.TryParse. Согласно Reference Source, тип Guid имеет мега-сложный конструктор, который проверяет все виды форматов guid и пытается их проанализировать. Нет вспомогательного метода, который вы можете вызвать, даже с помощью отражения. Я думаю, что вы должны искать сторонние парсеры Guid или написать свой собственный.


2

Запустите потенциальный GUID, хотя RegEx или некоторый пользовательский код, который проверяет работоспособность, чтобы убедиться, что стринг, по крайней мере, выглядит как GUID и состоит только из допустимых символов (и, возможно, он соответствует общему формату). Если он не пройдет проверку исправности, верните ошибку - это, вероятно, отсеет подавляющее большинство неверных строк.

Затем преобразуйте строку, как описано выше, по-прежнему перехватывая исключение для нескольких недопустимых строк, которые проходят проверку работоспособности.

Джон Скит (Jon Skeet) провел анализ чего-то похожего для разбора Ints (до того, как TryParse появился в Framework): проверка возможности преобразования строки в Int32

Однако, как указал AnthonyWJones, вам, вероятно, не стоит беспокоиться об этом.


1
 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }

"-" "{" "}" ("и") "не являются допустимыми шестнадцатеричными символами, но допустимы в строке guid.
Престон Гильо

2
и этот код будет отлично работать, если строка ввода guid содержит эти не шестнадцатеричные символы
rupello

1
  • Получить Отражатель
  • copy'n'paste. Guid's .ctor (String)
  • замените каждое вхождение «throw new ...» на «return false».

Ctor Guid - это в значительной степени скомпилированное регулярное выражение, так что вы получите точно такое же поведение без дополнительных затрат на исключение.

  1. Это реверс-инжиниринг? Я думаю, что так и может быть незаконно.
  2. Сломается при изменении формы GUID.

Еще более удачным решением было бы динамическое применение метода, заменив «бросить новый» на лету.


1
я пытался украсть код из ctor, но он ссылается на многие внутренние частные классы для выполнения своей работы поддержки. Поверьте мне, это была моя первая попытка.
Ян Бойд

1

Я голосую за ссылку GuidTryParse, размещенную выше Джоном или аналогичным решением (IsProbblyGuid). Я напишу такой же для моей библиотеки конверсий.

Я думаю, что это совершенно неубедительно, что этот вопрос должен быть настолько сложным. Ключевое слово "is" или "as" было бы просто замечательно, если бы Guid мог быть нулевым. Но по какой-то причине, хотя с SQL Server все в порядке, .NET - нет. Зачем? Какова стоимость Guid.Empty? Это просто глупая проблема, созданная при разработке .NET, и она действительно беспокоит меня, когда соглашения о языке наступают сами собой. Самый эффективный ответ до сих пор - использование COM Interop, потому что Framework не справляется с этим изящно? "Может ли эта строка быть GUID?" должен быть вопрос, на который легко ответить.

Можно полагаться на исключение, пока приложение не появится в Интернете. В этот момент я просто настроился на атаку отказа в обслуживании. Даже если я не буду «атакован», я знаю, что некоторые из них собираются поэкспериментировать с URL-адресом, или, возможно, мой отдел маркетинга отправит неверно сформированную ссылку, и тогда мое приложение должно испытать довольно высокую производительность, которую МОЖЕТ принести на сервере, потому что я не написал свой код для решения проблемы, которая НЕ ДОЛЖНА произойти, но мы все знаем, что БУДЕТ.

Это немного размывает строку «Исключение» - но суть в том, что даже если проблема нечастая, если за короткое время может произойти достаточное количество раз, когда ваше приложение аварийно завершает работу, отлавливая все эти ловушки, то я думаю, что выбрасывание исключения плохой тон.

TheRage3K



0
Private Function IsGuidWithOptionalBraces(ByRef strValue As String) As Boolean
    If String.IsNullOrEmpty(strValue) Then
        Return False
    End If

    Return System.Text.RegularExpressions.Regex.IsMatch(strValue, "^[\{]?[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}[\}]?$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)
End Function


Private Function IsGuidWithoutBraces(ByRef strValue As String) As Boolean
    If String.IsNullOrEmpty(strValue) Then
        Return False
    End If

    Return System.Text.RegularExpressions.Regex.IsMatch(strValue, "^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)
End Function


Private Function IsGuidWithBraces(ByRef strValue As String) As Boolean
    If String.IsNullOrEmpty(strValue) Then
        Return False
    End If

    Return System.Text.RegularExpressions.Regex.IsMatch(strValue, "^\{[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}\}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)
End Function

0

С методом расширения в C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.