İstisnaları atmadan dizenin bir kılavuz olup olmadığını test et?


180

Bir dizeyi bir Guid'e dönüştürmeyi denemek istiyorum, ancak istisnaları yakalamaya güvenmek istemiyorum (

  • performans nedeniyle - istisnalar pahalıdır
  • kullanılabilirlik nedeniyle - hata ayıklayıcı açılır
  • tasarım nedenleriyle - beklenen istisna değildir

Başka bir deyişle, kod:

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

uygun değil.

RegEx kullanmayı denemek istiyorum, ama kılavuz parantez sarılmış olabilir, brace sarılmış, hiçbiri sarılmış, zorlaştırır.

Ayrıca, bazı Guid değerlerinin geçersiz olduğunu düşündüm (?)


Güncelleme 1

ChristianK'ın hepsinden FormatExceptionziyade yakalamak için iyi bir fikri vardı . Sorunun kod örneğini öneri içerecek şekilde değiştirdi.


Güncelleme 2

Atılan istisnalar için neden endişeleniyorsunuz? Gerçekten sık sık geçersiz GUID bekliyorum?

Cevap evet . Ben - Ben TryStrToGuid kullanıyorum nedeni budur am hatalı veri bekliyor.

Örnek 1 Ad alanı uzantıları, klasör adına bir GUID eklenerek belirtilebilir . Klasör adlarını ayrıştırabilirim, finalden sonra metnin olup olmadığını kontrol edebilirim . bir GUID'dir.

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

Örnek 2 Çok kullanılan bir web sunucusu çalıştırıyor olabilirim ve gönderilen bazı verilerin geçerliliğini kontrol etmek isterim. Geçersiz veri kaynaklarının olması gerekenden 2-3 büyüklükte daha yüksek olmasını istemiyorum.

Örnek 3 Bir kullanıcı tarafından girilen bir arama ifadesini ayrıştırıyor olabilirim.

resim açıklamasını buraya girin

GUID'leri girerse, bunları özel olarak işlemek istiyorum (özellikle o nesneyi aramak veya yanıt metninde belirli bir arama terimini vurgulamak ve biçimlendirmek gibi).


Güncelleme 3 - Performans karşılaştırmaları

10.000 iyi Rehber ve 10.000 kötü Rehber dönüştürmeyi test edin.

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 Bir soruyu haklı göstermek zorunda olmamalıyım.


7
Neden dünyada bu bir topluluk wiki'si?
Jeff

36
Haklısın; Eğer gereken değil bir sorum haklı gerekiyor. Ancak, ben neden bu yüzden burada okuyorum neden çok benzer olan gerekçeyle okuyun. Bu yüzden, büyük gerekçe için teşekkürler.
bw

2
@Jeff muhtemelen OP 10 kez daha düzenledi çünkü - topluluk wiki meta
Marijn

3
Guid.TryParse veya Guid.TryParseExact ile ilgili çözümler için lütfen bu sayfayı aramaya devam edin. .NET 4.0 + ile yukarıdaki çözüm en şık değil
dplante

1
@dplante Soruyu ilk olarak 2008'de sorduğumda, hiç yoktu 4.0. Bu yüzden soru ve kabul edilen cevap, oldukları gibi.
Ian Boyd

Yanıtlar:


107

Performans Deneyleri

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 (En Hızlı) Cevabı:

/// <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);
    }
}

Alt satır: Bir dizenin kılavuz olup olmadığını kontrol etmeniz gerekiyorsa ve performansı önemsiyorsanız, COM Interop kullanın.

Dize temsilindeki bir kılavuzu bir Rehber'e dönüştürmeniz gerekiyorsa,

new Guid(someString);

8
Bunları hata ayıklayıcı ile çalıştırdınız veya kapattınız mı? Kural dışı durum atma performansı, hata ayıklayıcıyı takmadan birkaç kat artırılır.
Daniel T.

teşekkür ederim. Bu soruyu kendim sormak üzereydim. Cevabını bulduğuma sevindim.
David

Yukarıdan PInvoke kod snippet adıyla PInvoke.cs adlı yeni bir dosya oluşturdum, ancak kodun çalışmasını sağlayamıyorum. Hata ayıkladığımda CLSIDFromString sonucunun HER ZAMAN negatif olduğunu görüyorum. Çağıran hattı değiştirmeyi denedim: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), çıkış değeri); ama yine de her zaman olumsuzdur. Neyi yanlış yapıyorum?
JALLRED


65

Bunu sevmeyeceksin ama istisnayı yakalamanın daha yavaş olacağını düşündüren şey nedir?

Başarılı olanlara kıyasla kaç tane GUID ayrıştırma denemesi bekliyorsunuz?

Tavsiyem, az önce oluşturduğunuz işlevi kullanmanız ve kodunuzu profillemeniz. Bu işlevin gerçekten bir sıcak nokta olduğunu görürseniz, o zaman düzeltin, ancak daha önce değil.


2
İyi cevap, erken optimizasyon tüm kötülüklerin köküdür.
Kev

33
İstisnai olmayan istisnalara güvenmek zayıf bir formdur. Kimsenin girmesini istemediğim kötü bir alışkanlık. Ve özellikle insanların çalıştığına ve iyi çalıştığına güvenecekleri bir kütüphane rutininde yapmak istemem.
Ian Boyd

Anonim, orijinal sorunuzda istisnalardan kaçınmak istediğinizin nedeni olarak performans belirtildi. Eğer böyle değilse, belki de sorunuzu değiştirmelisiniz.
AnthonyWJones

6
İstisna EXCEPTIONNAL örneklerinin anlamında kullanılmalıdır: geliştirici tarafından yönetilmez. Hataları yönetmek için Microsoft'un 'istisna' yöntemine karşıyım. Savunma programlama kuralları. Lütfen Microsoft framework geliştiricileri, Guid sınıfına bir 'TryParse' eklemeyi düşünün.
Mose

14
kendi yorumuma cevap olarak => Guid.TryParse bu tür hızlı bir tepki için çerçeve 4.0 --- msdn.microsoft.com/en-us/library/… --- thxs MS eklenmiştir ;)
Mose

39

.NET 4.0'da aşağıdaki gibi yazabilirsiniz:

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

3
Bu gerçekten en iyi cevaplardan biri olmalı.
Tom Lint

21

En azından şöyle yeniden yazarım:

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

SEHException, ThreadAbortException veya diğer önemli veya ilişkili olmayan şeylerde "geçersiz GUID" demek istemezsiniz.

Güncelleme : .NET 4.0'dan başlayarak, Guid için yeni bir yöntem kümesi vardır:

Gerçekten, bunlar kullanılmalıdır (sadece gerçeği için, dahili olarak try-catch kullanılarak "saf olarak" uygulanmadılar).


13

Birlikte çalışma sadece istisnayı yakalamaktan daha yavaştır:

10.000 Rehber ile mutlu yolda:

Exception:    26ms
Interop:   1,201ms

Mutsuz yolda:

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

Daha tutarlı, ama aynı zamanda sürekli daha yavaş. Bana göre, hata ayıklayıcınızı yalnızca işlenmeyen istisnaları bozacak şekilde yapılandırmanız daha iyi olur.


"hata ayıklayıcı sadece işlenmeyen istisnalar kırmak için" bir seçenek değil.
Ian Boyd

1
@Ian Boyd - Sen (Express dahil) VS sürümleri herhangi birini kullanıyorsanız, bu ise bir seçenek. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett

1
yani uygulanabilir bir seçenek değil. "Arıza bir seçenek değil." Bu ise bir seçenek, ama kullanmak gitmiyorum o.
Ian Boyd

9

İşte ihtiyacınız olan normal ifade ...

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

Ama bu sadece yeni başlayanlar için. Ayrıca tarih / saat gibi çeşitli bölümlerin kabul edilebilir aralıklarda olduğunu doğrulamanız gerekir. Bunun, daha önce özetlediğiniz try / catch yönteminden daha hızlı olduğunu düşünemiyorum. Umarım bu tür bir kontrolü garanti etmek için birçok geçersiz GUID almazsınız!


Um, bir zaman damgasından üretilen IIRC GUID'leri genellikle kötü bir fikir olarak kabul edilir ve diğer tür (tip 4) tamamen randome olur
BCS

5

kullanılabilirlik nedeniyle - hata ayıklayıcı açılır

Deneme / yakalama yaklaşımına gidiyorsanız, hata ayıklayıcının atışta kırılmaya ayarlamış olsanız bile kırılmadığından emin olmak için [System.Diagnostics.DebuggerHidden] özelliğini ekleyebilirsiniz.


4

Hataları kullanmanın daha pahalı olduğu doğru olsa da , çoğu insan GUID'lerinin çoğunluğunun bilgisayar tarafından oluşturulacağına inanıyor, TRY-CATCHbu yüzden sadece maliyet ürettiği için çok pahalı değil CATCH. Bunu, ikisinin basit bir testiyle (kullanıcı genel, şifre yok) kendinize kanıtlayabilirsiniz .

Hadi bakalım:

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

Benzer bir durum vardı ve neredeyse hiç geçersiz karakter 36 karakter uzunluğunda olmadığını fark ettim. Bu gerçeğe dayanarak, hala basit tutarken daha iyi performans elde etmek için kodunuzu biraz değiştirdim.

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, kasasındaki kesikli dize formundan daha fazlasını kabul eder. GUID'lerde tire ile çevreleyen kıvırcık diş telleri bulunabilir veya tire veya diş telleri olmayabilir. Bu kod, bu alternatif ancak mükemmel şekilde geçerli dize formları tarafından kullanıldığında yanlış negatifler oluşturur.
Chris Charabaruk

1
Takip etmek için, dize biçimli GUID'ler için geçerli uzunluklar sırasıyla 32, 36 ve 38'dir - saf altıgen, kesikli ve kesikli teller.
Chris Charabaruk

1
@Chris, sizin puanınız geçerlidir, ancak @JBrooks, try / catch'a girmeden önce olası GUID'yi kontrol etme akıl sağlığı fikrini, özellikle şüpheli girdi yaygınsa mantıklıdır. Belki if gibi bir şey (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
bw

1
Gerçekten de, menzili 30.40 yerine 32..38 daha sıkı tutsam da daha iyi olurdu.
Chris Charabaruk

2

Bildiğim kadarıyla mscrolib'de Guid.TryParse gibi bir şey yok. Referans Kaynağa göre, Guid tipi her türlü guid formatını kontrol eden ve bunları ayrıştırmaya çalışan mega karmaşık bir yapıcıya sahiptir. Yansıtma yoluyla bile çağırabileceğiniz hiçbir yardımcı yöntem yoktur. Bence 3. taraf Guid ayrıştırıcılarını aramak ya da kendiniz yazmak zorundasınız.


2

Düzenlemenin en azından bir GUID'e benzediğinden ve yalnızca geçerli karakterlerden (ve belki de genel biçime uyuyor gibi göründüğünden) emin olmak için bir RegEx veya sağlık kontrolü yapan bazı özel kodlar kullanarak potansiyel GUID'i çalıştırın. Sağlık kontrolünü geçmezse bir hata döndürün - bu muhtemelen geçersiz dizelerin büyük çoğunluğunu ayıklayacaktır.

Daha sonra, aklı kontrolünü geçen birkaç geçersiz dize için istisnayı yakalayarak, dizeyi yukarıdaki gibi dönüştürün.

Jon Skeet, Ints'i ayrıştırmak için benzer bir şey için bir analiz yaptı (TryParse, Framework'te bulunmadan önce): Bir dizenin Int32'ye dönüştürülüp dönüştürülemeyeceğini kontrol etme

Ancak, AnthonyWJones'in belirttiği gibi, muhtemelen bu konuda endişelenmemelisiniz.


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

"-" "{" "}" ("ve") "geçerli onaltılık karakterler değil, ancak bir kılavuz dizede geçerli.
Preston Guillot

2
ve giriş
kılavuzu

1
  • Reflektör Alın
  • copy'n'paste Guid's .ctor (Dize)
  • "yeni atış ..." her vesilesiyle "return false" ifadesini değiştirin.

Guid'in kralı derlenmiş bir regex'tir, bu şekilde istisna yükü olmadan tam olarak aynı davranışı elde edersiniz.

  1. Bu tersine bir mühendislik midir? Bence öyle ve yasa dışı da olabilir.
  2. GUID formu değişirse kırılır.

Daha soğuk bir çözüm bile, anında "yeni fırlatma" yı değiştirerek bir yöntemi dinamik olarak uygulamak olacaktır.


1
Ben ctor kodunu çalmaya çalıştı, ancak destek çalışmasını gerçekleştirmek için birçok iç özel sınıf referanslar. İnanın ilk denemem buydu.
Ian Boyd

1

Jon veya benzer bir çözüm (IsProbablyGuid) tarafından yayınlanan GuidTryParse bağlantısına oy veriyorum. Dönüşüm kütüphanem için buna benzer bir tane yazacağım.

Bence bu sorunun bu kadar karmaşık olması çok topal. "Bir" veya "as" anahtar kelimesi, bir Rehber'in boş olması durumunda iyi olur. Ancak bir nedenden dolayı, SQL Server bununla iyi olsa da, .NET değildir. Neden? Guid.Empty'nin değeri nedir? Bu, .NET tasarımı tarafından yaratılan aptalca bir sorundur ve bir dilin kuralları kendine geldiğinde gerçekten beni rahatsız ediyor. Şimdiye kadar en iyi performans gösteren yanıt COM Interop'u kullanmaktaydı, çünkü Framework bunu düzgün bir şekilde ele almıyor mu? "Bu dize bir GUID olabilir mi?" cevaplaması kolay bir soru olmalı.

Uygulama internete girene kadar atılan istisna güvenmek, sorun yok. Bu noktada kendimi bir hizmet reddi saldırısı için ayarladım. "Saldırıya" maruz kalmasam bile, bazı yahoo'nun URL ile maymunlaşacağını biliyorum, ya da belki de pazarlama departmanım hatalı biçimlendirilmiş bir bağlantı gönderecek ve daha sonra başvurumun COULD'un getireceği oldukça ağır bir performans isabeti çekmesi gerekiyor. sunucuyu aşağı çünkü ben OLMAMALIDIR bir sorunu ele kodumu yazmadım, ama hepimiz OLACAK OLACAKTIR.

Bu, "İstisna" üzerinde çizgiyi biraz bulanıklaştırır - ancak alt satırda, sorun sık olmasa bile, uygulamanızın her şeyi yakalama hizmetine hizmet etmesi için kısa bir süre içinde yeterli olursa, o zaman bir istisna atmak olduğunu düşünüyorum kötü form.

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 # 'da bir uzantı yöntemi ile

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.