Перевірте, чи рядок є орієнтиром, не кидаючи винятків?


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 недійсні (?)


Оновлення 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
Ти маєш рацію; ви не повинні обґрунтовувати питання. Однак я читаю виправдання з цікавістю (оскільки це дуже схоже на те, чому я тут це читаю). Тож дякую за велике виправдання.
bw

2
@Jeff, швидше за все, ОП редагував це більше 10 разів - див. Мета на wiki спільноти
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);
    }
}

Підсумок: Якщо вам потрібно перевірити, чи є рядок орієнтиром, і ви дбаєте про продуктивність, використовуйте COM Interop.

Якщо вам потрібно перетворити настанову в String-представництві в Guid, використовуйте

new Guid(someString);

8
Ви запускали їх із відладчиком увімкнено чи вимкнено? Продуктивність викидання винятків покращується в кілька разів без приєднання налагоджувача.
Даніель Т.

спасибі. Я збирався сам задати це питання. Радий, що знайшов вашу відповідь.
Девід

Я створив новий файл під назвою PInvoke.cs з фрагментом коду PInvoke зверху, але я не можу змусити код працювати. Під час налагодження я бачу, що результат CLSIDFromString ЗАВЖДИ негативний. Я спробував змінити викликову лінію на: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); але це завжди завжди негативно. Що я роблю неправильно?
ВЗАЄМО

88

Щойно .net 4.0 доступний, який ви можете використовувати Guid.TryParse().


8
Ще один швидший спосіб - за допомогою методу Guid.TryParseExact ().

4
Якщо розбір рядків Guid є найповільнішою частиною вашої програми, то вас благословлять.
Повернення коштів не повернено

65

Вам це не сподобається, але що змушує вас думати, що ловити виняток буде повільніше?

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

Моя порада - використовувати функцію, яку ви тільки що створили, і профайлювати ваш код. Якщо ви виявите, що ця функція справді є гарячою точкою, тоді її виправте, але не раніше.


2
Гарна відповідь, передчасна оптимізація - корінь усього зла.
Кев

33
Погана форма покладатися на винятки, які не є винятковими. Це погана звичка, що я не хотів би, щоб хтось заходив. І я особливо не хотів би робити це в бібліотечному розпорядку, де люди будуть довіряти, що це працює і добре.
Ян Бойд

Анонімно, ваше первісне запитання вказало на ефективність як причину того, щоб ви хотіли уникати винятків. Якщо це не так, то, можливо, варто поставити своє запитання.
AnthonyWJones

6
Виняток має використовуватися в значеннях EXCEPTIONNAL: не керується розробником. Я супротивник Майкрософт 'все виняток' спосіб управління помилками. Правила оборонного програмування. Будь-ласка, розробники каркасів 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 доступний новий набір методів:

Дійсно, їх слід використовувати (хоч би лише на те, що вони не «наївно» реалізуються, використовуючи «внутрішній пробір»).


13

Interop повільніше, ніж просто вилов винятку:

На щасливому шляху, з 10000 Посібників:

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}(}})$

Але це лише для початку. Вам також доведеться переконатися, що різні частини, такі як дата / час, знаходяться в допустимих межах. Я не можу уявити, що це швидше, ніж метод спробу / лову, який ви вже намітили. Сподіваємось, ви не отримуєте стільки недійсних GUID, щоб гарантувати цей вид перевірки!


Гм, посилання GUI IIRC, що генеруються за допомогою часової позначки, як правило, вважаються поганою ідеєю, а інший вид (тип 4) - абсолютно непримітний
BCS

5

з міркувань зручності використання - налагоджувач вискакує

Якщо ви збираєтеся спробувати підхопити, ви можете додати атрибут [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 перед тим, як увійти в спробу / catch має сенс, особливо якщо підозрілий вклад є загальним. Можливо щось на зразок if (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
bw

1
Дійсно, це було б краще, хоча я б тримав діапазон жорсткішим, 32..38, а не 30..40.
Кріс Чарабарук

2

Наскільки я знаю, у mscrolib немає нічого подібного Guid.TryParse. Відповідно до довідкового джерела, тип Guid має мегаскладний конструктор, який перевіряє всі види форматів керівництва та намагається їх розібрати. Не існує жодного допоміжного методу, якого можна зателефонувати, навіть через роздуми. Я думаю, що вам доведеться шукати сторонніх аналізаторів Guid або написати свій власний.


2

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

Потім конвертуйте рядок, як у вас було вище, все ще вловлюючи виняток для кількох недійсних рядків, які потрапляють через перевірку правильності.

Джон Скіт зробив аналіз на щось подібне для розбору Ints (до того, як TryParse був у рамках): Перевірка, чи може рядок можна перетворити на Int32

Однак, як зазначав AnthonyWJones, ви, мабуть, не повинні турбуватися з цього приводу.


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

"-" "{" "}" ("і") "не є шістнадцятковими символами, але вони є дійсними в рядку настанови.
Престон Гільо,

2
і цей код буде чудово працювати, якщо вхідний рядок введення містить ці шістнадцяткові символи
rupello

1
  • Отримайте рефлектор
  • copy'n'paste Guid's .ctor (String)
  • замініть кожну подію "кинути нове ..." на "повернути помилковим".

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

  1. Це означає зворотну інженерію? Я думаю, що це так і може бути незаконним.
  2. Порушиться, якщо зміниться форма GUID.

Ще крутішим рішенням було б динамічно інструментувати метод, замінюючи "кидати нове" на льоту.


1
Я намагався вкрасти код з ctor, але він посилається на багато внутрішніх приватних класів, щоб виконати свою підтримку. Повірте, це була моя перша спроба.
Ян Бойд

1

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

Я думаю, що це зовсім кульгаво, що це питання має бути таким складним. Ключове слово "є" або "як" було б чудово, якби Посібник міг бути нульовим. Але чомусь, навіть незважаючи на те, що з SQL Server це нормально, .NET це не так. Чому? Яке значення Guid.Empty? Це просто дурна проблема, створена дизайном .NET, і вона насправді клопоче про мене, коли конвенції мови наступають на себе. Найпопулярнішою відповіддю досі було використання COM Interop, оскільки Framework не справляється з цим витончено? "Чи може цей рядок бути GUID?" повинно бути питання, на яке легко відповісти.

Посилаючись на викид, що викидається, все в порядку, поки додаток не з’явиться в Інтернеті. У цей момент я просто налаштувався на атаку у відмові у службі. Навіть якщо я не нападаю на "атаку", я знаю, що хтось із Yahoo збирається мавпати з 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.