Проверить, является ли string идентификатором guid без создания исключений?
Вопрос
Я хочу попытаться преобразовать строку в 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 Возможно, я анализирую поисковое выражение, введенное пользователем.
Если они вводят 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 в значительной степени является скомпилированным регулярным выражением, таким образом, вы получите точно такое же поведение без накладных расходов на исключение.
- Представляет ли это собой обратный инжиниринг?Я думаю, что это так, и как таковое может быть незаконным.
- Сломается, если форма 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);
}