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

StackOverflow https://stackoverflow.com/questions/104850

  •  01-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

это не подходит.

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

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


Обновление 1

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


Обновление 2

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

Ответ таков ДА.Вот почему я использую TryStrToGuid - I ам ожидая неверных данных.

Пример 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 Возможно, я анализирую поисковое выражение, введенное пользователем.

enter image description here

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


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

Протестируйте преобразование 10 000 хороших Guid и 10 000 плохих Guid.

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

p.s.Я не должен был оправдывать свой вопрос.

Это было полезно?

Решение

Контрольные показатели эффективности

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 в строковом представлении в Guid, используйте

new Guid(someString);

Другие советы

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

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

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

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

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

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

Я бы, по крайней мере, переписал это как:

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

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

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

Действительно, их следует использовать (хотя бы из-за того факта, что они не "наивно" реализованы с помощью try-catch внутренне).

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

На счастливом пути, с 10 000 идентификаторами Guid:

Exception:    26ms
Interop:   1,201ms

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

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

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

Что ж, вот регулярное выражение, которое вам понадобится...

^[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, чтобы оправдать проверку такого типа!

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

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

В то время как это является верно, что использование ошибок обходится дороже, большинство людей считают, что большинство их GUID будут сгенерированы компьютером, поэтому 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;
  }

У меня была похожая ситуация, и я заметил, что почти никогда недопустимая строка не была длиной в 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;
    }
}

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

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

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

Джон Скит провел анализ чего-то подобного для синтаксического анализа целых чисел (до того, как TryParse появился в фреймворке): Проверка того, может ли строка быть преобразована в Int32

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

 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }
  • Получить Отражатель
  • скопируйте и вставьте Guid's .ctor(Строку)
  • заменяйте каждое появление "throw new ..." на "return false".

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

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

Еще более крутым решением было бы динамически инструментализировать метод, заменив "throw new" "на лету".

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

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

Полагаться на генерируемое исключение - это нормально, пока приложение не выйдет в Интернет.В тот момент я просто настроил себя на атаку типа "отказ в обслуживании".Даже если я не подвергнусь "атаке", я знаю, что какой-нибудь yahoo будет манипулировать URL-адресом, или, возможно, мой отдел маркетинга отправит неверно оформленную ссылку, и тогда моему приложению придется столкнуться с довольно значительным снижением производительности, которое МОЖЕТ привести к отключению сервера, потому что я не написал свой код для решения проблемы, которая НЕ ДОЛЖНА была возникнуть, но мы все знаем, что ПРОИЗОЙДЕТ.

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

TheRage3K

если TypeOf ctype(myvar,Object) Является Guid, то .....

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

С помощью метода расширения на C#

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top