Uji apakah string adalah panduan tanpa melemparkan pengecualian?


180

Saya ingin mencoba mengubah string menjadi Guid, tetapi saya tidak ingin bergantung pada menangkap pengecualian (

  • karena alasan kinerja - pengecualian mahal
  • untuk alasan kegunaan - debugger akan muncul
  • untuk alasan desain - yang diharapkan tidak luar biasa

Dengan kata lain kodenya:

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

tidak cocok.

Saya akan mencoba menggunakan RegEx, tetapi karena panduan dapat dibungkus tanda kurung, dibungkus kurung, tidak ada yang dibungkus, membuatnya sulit.

Selain itu, saya pikir nilai Guid tertentu tidak valid (?)


Perbarui 1

ChristianK punya ide bagus untuk menangkap saja FormatException, daripada semua. Mengubah contoh kode pertanyaan untuk menyertakan saran.


Perbarui 2

Mengapa khawatir tentang pengecualian yang dilemparkan? Apakah saya benar-benar mengharapkan GUID tidak valid sesering itu?

Jawabannya adalah ya . Itulah mengapa saya menggunakan TryStrToGuid - Saya sedang mengharapkan data yang buruk.

Contoh 1 Ekstensi ruang nama dapat ditentukan dengan menambahkan GUID ke nama folder . Saya mungkin parsing nama folder, memeriksa untuk melihat apakah teks setelah final . adalah 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

Contoh 2 Saya mungkin menjalankan server web yang banyak digunakan ingin memeriksa validitas beberapa data yang diposkan kembali. Saya tidak ingin data yang tidak valid mengikat sumber daya 2-3 pesanan lebih besar dari yang seharusnya.

Contoh 3 Saya mungkin mem-parsing ekspresi pencarian yang dimasukkan oleh pengguna.

masukkan deskripsi gambar di sini

Jika mereka memasukkan GUID, saya ingin memprosesnya secara khusus (seperti mencari objek itu secara khusus, atau menyorot dan memformat istilah pencarian tertentu dalam teks respons.)


Perbarui 3 - Tolok ukur kinerja

Uji konversi 10.000 Panduan yang baik, dan 10.000 Panduan yang buruk.

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 saya tidak perlu membenarkan pertanyaan.


7
Kenapa di dunia ini ada komunitas wiki?
Jeff

36
Kamu benar; Anda tidak harus membenarkan pertanyaan. Namun, saya membaca justifikasi dengan minat (karena sangat mirip dengan mengapa saya di sini membaca ini). Jadi, terima kasih atas pembenarannya.
bw

2
@ Jeff mungkin karena OP telah mengeditnya lebih dari 10 kali - lihat meta di komunitas wiki
Marijn

3
Silakan terus mencari di halaman ini untuk solusi dengan Guid.TryParse atau Guid.TryParseExact. Dengan .NET 4.0 + solusi di atas bukanlah yang paling elegan
dplante

1
@dplante Ketika saya awalnya mengajukan pertanyaan pada tahun 2008, tidak ada 4.0. Itu sebabnya pertanyaan, dan jawaban yang diterima, adalah cara mereka.
Ian Boyd

Jawaban:


107

Tolok Ukur Kinerja

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 (Tercepat) Jawaban:

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

Intinya: Jika Anda perlu memeriksa apakah string adalah panduan, dan Anda peduli tentang kinerja, gunakan COM Interop.

Jika Anda perlu mengonversi panduan dalam representasi String ke Panduan, gunakan

new Guid(someString);

8
Apakah Anda menjalankan ini dengan atau tidak debugger? Kinerja melempar pengecualian ditingkatkan beberapa kali lipat tanpa melampirkan debugger.
Daniel T.

Terima kasih. Saya sendiri akan mengajukan pertanyaan ini. Senang saya menemukan jawaban Anda.
David

Saya telah membuat file baru bernama PInvoke.cs dengan potongan kode PInvoke namespace dari atas, tetapi saya tidak bisa membuat kode berfungsi. Ketika saya men-debug saya melihat bahwa hasil CLSIDFromString SELALU negatif. Saya mencoba mengubah saluran panggilan ke: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), nilai keluar); tetapi itu selalu selalu negatif. Apa yang saya lakukan salah?
JALLRED


65

Anda tidak akan menyukai ini, tetapi apa yang membuat Anda berpikir bahwa menangkap pengecualian akan menjadi lebih lambat?

Berapa banyak upaya gagal untuk menguraikan GUID yang Anda harapkan dibandingkan dengan yang berhasil?

Saran saya adalah gunakan fungsi yang baru saja Anda buat dan profil kode Anda. Jika Anda menemukan bahwa fungsi ini benar-benar hotspot maka perbaiki tetapi tidak sebelumnya.


2
Jawaban bagus, optimasi prematur adalah akar dari semua kejahatan.
Kev

33
Bentuknya buruk bergantung pada pengecualian yang tidak luar biasa. Ini kebiasaan buruk yang tidak ingin ada orang masuk. Dan saya terutama tidak ingin melakukannya dalam rutinitas perpustakaan di mana orang akan percaya bahwa itu bekerja dengan baik.
Ian Boyd

Anonim, pertanyaan awal Anda menyatakan kinerja sebagai alasan Anda ingin menghindari pengecualian. Jika tidak demikian maka mungkin Anda harus mengubah pertanyaan Anda.
AnthonyWJones

6
Pengecualian harus digunakan dalam arti kasus LUAR BIASA: tidak dikelola oleh pengembang. Saya menentang cara Microsoft 'semua pengecualian' dalam mengelola kesalahan. Aturan pemrograman defensif. Tolong kerangka kerja Microsoft, pertimbangkan untuk menambahkan 'TryParse' ke kelas Guid.
Mose

14
dalam menanggapi komentar saya sendiri => Guid.TryParse telah ditambahkan ke framework 4.0 --- msdn.microsoft.com/en-us/library/… --- thxs MS untuk reaksi cepat;)
Mose

39

Di .NET 4.0 Anda dapat menulis sebagai berikut:

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

3
Ini harus benar-benar menjadi salah satu jawaban teratas.
Tom Lint

21

Setidaknya saya akan menulis ulang sebagai:

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

Anda tidak ingin mengatakan "GUID tidak valid" pada SEHException, ThreadAbortException atau hal-hal fatal atau tidak terkait lainnya.

Pembaruan : Dimulai dengan .NET 4.0, ada serangkaian metode baru yang tersedia untuk Guid:

Sungguh, itu harus digunakan (jika hanya untuk fakta, bahwa mereka tidak "naif" diimplementasikan menggunakan try-catch secara internal).


13

Interop lebih lambat dari sekedar menangkap pengecualian:

Di jalan yang bahagia, dengan 10.000 Panduan:

Exception:    26ms
Interop:   1,201ms

Di jalan yang tidak bahagia:

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

Ini lebih konsisten, tetapi juga lebih lambat secara konsisten. Menurut saya, Anda sebaiknya mengkonfigurasi debugger Anda hanya untuk istirahat pada pengecualian yang tidak tertangani.


"debugger Anda hanya mematahkan pengecualian yang tidak ditangani" Bukan pilihan.
Ian Boyd

1
@Ian Boyd - Jika Anda menggunakan salah satu edisi VS (termasuk Express), itu adalah opsi. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett

1
maksud saya itu bukan pilihan yang masuk akal. Seperti, "Kegagalan bukan pilihan." Ini adalah opsi, tetapi saya tidak akan menggunakannya.
Ian Boyd

9

Nah, inilah regex yang Anda butuhkan ...

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

Tapi itu hanya untuk pemula. Anda juga harus memverifikasi bahwa berbagai bagian seperti tanggal / waktu berada dalam rentang yang dapat diterima. Saya tidak bisa membayangkan ini menjadi lebih cepat daripada metode coba / tangkap yang telah Anda uraikan. Semoga Anda tidak menerima banyak GUID yang tidak valid untuk menjamin jenis cek ini!


Um, PANDUAN IIRC yang dihasilkan dari cap waktu umumnya dianggap sebagai ide yang buruk dan jenis lainnya (tipe 4) benar-benar randome
BCS

5

untuk alasan kegunaan - debugger akan muncul

Jika Anda akan mencoba pendekatan coba / tangkap, Anda dapat menambahkan atribut [System.Diagnostics.DebuggerHidden] untuk memastikan debugger tidak merusak bahkan jika Anda telah menetapkannya untuk istirahat saat melempar.


4

Sementara itu adalah benar bahwa menggunakan kesalahan lebih mahal, kebanyakan orang percaya bahwa mayoritas GUID mereka akan menjadi komputer yang dihasilkan sehingga TRY-CATCHtidak terlalu mahal karena hanya menghasilkan biaya pada CATCH. Anda dapat membuktikan ini pada diri sendiri dengan tes sederhana keduanya (pengguna publik, tanpa kata sandi).

Ini dia:

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

Saya memiliki situasi yang sama dan saya perhatikan bahwa hampir tidak pernah ada string 36 karakter yang tidak valid. Jadi berdasarkan fakta ini, saya sedikit mengubah kode Anda untuk mendapatkan kinerja yang lebih baik sambil tetap membuatnya sederhana.

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 menerima lebih dari sekedar bentuk string putus-putus di ctornya. GUID dapat memiliki kurung kurawal di sekitarnya dengan garis putus-putus, atau bebas dari garis putus-putus atau kawat gigi. Kode ini akan menghasilkan false negative bila digunakan oleh string-string alternatif yang juga valid itu.
Chris Charabaruk

1
Untuk menindaklanjutinya, panjang yang valid untuk GUID bentuk-string adalah 32, 36, dan 38 - hex murni, putus-putus, dan kawat gigi-dengan-putus-putus, masing-masing.
Chris Charabaruk

1
@ Chris, poin Anda valid, tetapi gagasan @JBrooks tentang kewarasan memeriksa calon GUID sebelum melakukan try / catch masuk akal, terutama jika input yang dicurigai adalah umum. Mungkin sesuatu seperti if (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
bw

1
Memang, itu akan lebih baik, meskipun saya akan menjaga jangkauan lebih ketat, 32..38 daripada 30.40.
Chris Charabaruk

2

Sejauh yang saya tahu, tidak ada sesuatu seperti Guid.TryParse di mscrolib. Menurut Sumber Referensi, tipe Guid memiliki konstruktor mega-kompleks yang memeriksa semua jenis format panduan dan mencoba untuk menguraikannya. Tidak ada metode pembantu yang bisa Anda panggil, bahkan melalui refleksi. Saya pikir Anda harus mencari parser Guid pihak ketiga, atau menulis sendiri.


2

Jalankan potensi GUID melalui RegEx atau beberapa kode kustom yang melakukan pemeriksaan kewarasan untuk memastikan strig setidaknya terlihat seperti GUID dan hanya terdiri dari karakter yang valid (dan mungkin sepertinya cocok dengan format keseluruhan). Jika tidak lulus pemeriksaan kewarasan, kembalikan kesalahan - yang mungkin akan menyingkirkan sebagian besar string tidak valid.

Kemudian konversi string seperti yang Anda miliki di atas, masih menangkap pengecualian untuk beberapa string tidak valid yang melewati pemeriksaan kewarasan.

Jon Skeet melakukan analisis untuk sesuatu yang mirip dengan parsing Ints (sebelum TryParse ada dalam Framework): Memeriksa apakah string dapat dikonversi ke Int32

Namun, seperti yang ditunjukkan AnthonyWJones Anda mungkin tidak perlu khawatir tentang ini.


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

"-" "{" "}" ("dan") "bukan karakter hex yang valid, tetapi valid dalam string panduan.
Preston Guillot

2
dan kode ini akan bekerja dengan baik jika input guid string berisi karakter-karakter non-hex
rupello

1
  • Dapatkan Reflektor
  • copy'n'paste Guid's .tor (String)
  • ganti setiap kejadian "throw new ..." dengan "return false".

Ctor Guid adalah regex yang dikompilasi, sehingga Anda akan mendapatkan perilaku yang sama persis tanpa pengecualian.

  1. Apakah ini merupakan rekayasa terbalik? Saya pikir itu benar, dan karena itu mungkin ilegal.
  2. Akan pecah jika bentuk GUID berubah.

Bahkan solusi yang lebih keren adalah dengan instrumen dinamis metode, dengan mengganti "melemparkan baru" dengan cepat.


1
saya mencoba mencuri kode dari ctor, tetapi referensi banyak kelas privat internal untuk melakukan pekerjaan dukungannya. Percayalah, itu adalah percobaan pertama saya.
Ian Boyd

1

Saya memilih tautan GuidTryParse yang diposting di atas oleh Jon atau solusi serupa (IsProbablyGuid). Saya akan menulis yang seperti itu untuk perpustakaan Konversi saya.

Saya pikir itu benar-benar lumpuh bahwa pertanyaan ini harus sangat rumit. Kata kunci "is" atau "as" akan baik-baik saja JIKA seorang Guid bisa menjadi nol. Tetapi untuk beberapa alasan, meskipun SQL Server tidak masalah dengan itu, .NET tidak. Mengapa? Berapa nilai Guid.Empty? Ini hanya masalah konyol yang dibuat oleh desain. NET, dan itu benar-benar mengganggu saya ketika konvensi bahasa menginjak dirinya sendiri. Jawaban berkinerja terbaik sejauh ini telah menggunakan COM Interop karena Framework tidak menanganinya dengan anggun? "Bisakah string ini menjadi GUID?" harus menjadi pertanyaan yang mudah dijawab.

Mengandalkan pengecualian yang dilemparkan tidak apa-apa, sampai aplikasi berjalan di internet. Pada saat itu saya hanya mengatur diri saya untuk penolakan serangan layanan. Bahkan jika saya tidak "diserang", saya tahu beberapa yahoo akan mengunggah URL, atau mungkin departemen pemasaran saya akan mengirimkan tautan yang salah, dan kemudian aplikasi saya harus mengalami kinerja yang lumayan kuat yang BISA dibawa ke server karena saya tidak menulis kode saya untuk menangani masalah yang TIDAK HARUS terjadi, tetapi kita semua tahu AKAN TERJADI.

Ini mengaburkan garis sedikit pada "Pengecualian" - tetapi intinya, bahkan jika masalahnya jarang terjadi, jika itu bisa terjadi cukup banyak dalam jangka waktu pendek sehingga aplikasi Anda crash melayani tangkapan dari itu semua, maka saya pikir melempar pengecualian adalah bentuk buruk.

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

Dengan metode ekstensi dalam C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.