例外をスローせずに文字列がGUIDかどうかをテストしますか?


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は括弧で囲むことができるため、中かっこで囲み、何もラップしないと、困難になります。

さらに、特定の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使用頻度の高いWebサーバーを実行している可能性があり、ポストバックされたデータの有効性を確認する必要があります。無効なデータが必要以上に2〜3桁高いリソースを占有することを望まない。

例3ユーザーが入力した検索式を解析している可能性があります。

ここに画像の説明を入力してください

彼らがGUIDを入力した場合、それらを特別に処理したい(そのオブジェクトを具体的に検索する、または応答テキストでその特定の検索語を強調表示してフォーマットするなど)。


Update 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

PS私は質問を正当化する必要はありません。


7
なぜこれがコミュニティWikiなのですか?
Jeff

36
あなたが正しい; 質問を正当化する必要ありませ。しかし、私は興味を持って正当化を読みます(これを読んでいる理由と非常に似ているためです)。それでは、大きな正当化に感謝します。
bw

2
@ジェフはおそらく、OPが編集した回数が10回を超えているためです
Marijn

3
Guid.TryParseまたはGuid.TryParseExactの解決策については、このページを引き続きご覧ください。.NET 4.0 +では、上記のソリューションは最もエレガントではありません
dplante 14

1
@dplante 2008年に最初に質問したときはありませんでした4.0。だからこそ、質問と受け入れられた答えは、彼らのやり方です。
Ian Boyd

回答:


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(Fastest)の回答:

/// <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相互運用機能を使用します。

文字列表現のGUIDをGUIDに変換する必要がある場合は、

new Guid(someString);

8
デバッガをオンまたはオフにしてこれらを実行しましたか?デバッガーを接続しなくても、例外のスローのパフォーマンスが数倍向上します。
Daniel T.

ありがとうございました。私自身もこの質問をしようとしていました。よろしくお願いします。
デビッド

上記の名前空間PInvokeコードスニペットでPInvoke.csという新しいファイルを作成しましたが、コードを機能させることができません。デバッグすると、CLSIDFromStringの結果が常に負になることがわかります。呼び出し行を次のように変更してみました。int hresult = PInvoke.ObjBase.CLSIDFromString(Guid.NewGuid()。ToString()、out value); しかし、それはまだ常に否定的です。何が悪いのですか?
JALLRED 2014


65

あなたはこれが好きではありませんが、例外のキャッチが遅くなると思うのはなぜですか?

GUIDの解析に失敗した場合、成功した場合と比較して何回失敗すると予想されますか?

私のアドバイスは、先ほど作成した関数を使用して、コードをプロファイルすることです。この機能が本当にホットスポットであることがわかった場合は、それを修正してください。


2
良い答えは、時期尚早の最適化がすべての悪の根源です。
Kev

33
例外的ではない例外に依存するのは不適切な形式です。誰かに侵入して欲しくないのは悪い癖です。そして、私は特に、人々がそれがうまくいくと信じているライブラリルーチンでそれをしたくありません。
Ian Boyd

匿名の元の質問では、例外を避けたい理由としてパフォーマンスが述べられていました。そうでない場合は、おそらくあなたの質問を微調整する必要があります。
AnthonyWJones 2008

6
例外は、EXCEPTIONNALの場合に使用する必要があります。つまり、開発者によって管理されていません。私は、エラーを管理するMicrosoftの「すべての例外」の方法に反対しています。防御的なプログラミング規則。Microsoftフレームワーク開発者にお願いします。Guidクラスに「TryParse」を追加することを検討してください。
Mose

14
私自身のコメント=> Guid.TryParseに応じて---フレームワーク4.0に追加されましたmsdn.microsoft.com/en-us/library/...、このような迅速な反応のためにMS thxs ---;)
モーゼ

39

.NET 4.0では、次のように記述できます。

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

3
これは本当にトップ回答の1つになるはずです。
トム・リント

21

少なくとも次のように書き直します。

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

SEHException、ThreadAbortException、またはその他の致命的または関連のないもので「無効なGUID」と言いたくない。

更新:.NET 4.0以降、Guidで使用できる一連の新しいメソッドがあります。

実際には、これらを使用する必要があります(事実のためにのみ、内部的にtry-catchを使用して「単純に」実装されていません)。


13

相互運用は、例外をキャッチするよりも遅くなります。

10,000 GUIDのハッピーパス:

Exception:    26ms
Interop:   1,201ms

不幸な道のり:

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

より一貫性がありますが、一貫して遅いです。ハンドルされていない例外でのみブレークするようにデバッガーを構成した方がよいと私には思われます。


「未処理の例外でのみブレークするデバッガ」オプションではありません。
Ian Boyd、

1
@Ian Boyd-VSエディション(Expressを含む)のいずれかを使用している場合、それオプションです。msdn.microsoft.com/en-us/library/038tzxdw.aspx
Mark Brackett

1
私はそれが実行可能なオプションではないことを意味しました。「失敗は許されない」のように。これオプションですが、使用しないものです。
Ian Boyd、

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

しかし、それは初心者のためだけです。また、日付/時刻などのさまざまな部分が許容範囲内であることも確認する必要があります。これが、すでに概説したtry / catchメソッドよりも速いとは思えません。この種類のチェックを保証するために無効なGUIDがたくさん届かないことを願っています!


ええと、タイムスタンプから生成されたIIRC GUIDは一般に悪い考えであると見なされ、他の種類(タイプ4)は完全にランダムです
BCS

5

使いやすさの理由から-デバッガーがポップアップします

try / catchアプローチを使用する場合は、[System.Diagnostics.DebuggerHidden]属性を追加して、スロー時にブレークするように設定した場合でもデバッガーがブレークしないようにします。


4

エラーを使用するとコストが高くなるの事実ですが、ほとんどの人は、GUIDの大部分がコンピューターで生成されると考えているため、TRY-CATCHでコストが発生するだけなので、それほど高価ではありませんCATCH2つの簡単なテスト(ユーザーパブリック、パスワードなし)を使用して、これを自分で証明できます。

どうぞ:

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は、ダッシュで囲まれた中かっこで囲むことも、ダッシュや中かっこで囲むこともできます。このコードは、代替の、しかし完全に有効な文字列形式で使用すると、偽陰性を生成します。
Chris Charabaruk、2009

1
補足として、文字列形式のGUIDの有効な長さは、それぞれ32、36、および38(それぞれ、純粋な16進数、破線、および中かっこ付きのダッシュ)です。
Chris Charabaruk、2009

1
@Chris、あなたのポイントは有効ですが、特に疑わしい入力が一般的である場合は、try / catchにバレルする前にGUID候補をサニティチェックする@JBrooksのアイデアは理にかなっています。多分if(value == null || value.Length <30 || value.length> 40){value = Guid.Empty; return false;}
bw

1
確かに、私は範囲を30..40ではなく32..38に狭めていますが、それはより良いでしょう。
Chris Charabaruk、2010

2

私の知る限り、mscrolibにはGuid.TryParseのようなものはありません。参考資料によると、Guid型にはすべての種類のGUID形式をチェックして解析を試みるメガコンプレックスコンストラクターがあります。リフレクションを介しても、呼び出すことができるヘルパーメソッドはありません。サードパーティのGuidパーサーを検索するか、独自に作成する必要があると思います。


2

RegExまたは正常性チェックを行うカスタムコードを使用して、潜在的なGUIDを実行し、strigが少なくともGUIDのように見え、有効な文字のみで構成されていることを確認します(おそらく全体の形式に適合しているようです)。妥当性検査に合格しない場合はエラーが返されます。これにより、無効な文字列の大部分が除外されます。

次に、上記のように文字列を変換しますが、正常性チェックを通過するいくつかの無効な文字列の例外をキャッチします。

Jon SkeetがIntを解析するための類似の分析を行いました(TryParseがフレームワークに入る前): 文字列をInt32に変換できるかどうかを確認しています

ただし、AnthonyWJonesが示したように、おそらくこれについて心配する必要はありません。


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

"-" "{" "}"( "および") "は有効な16進文字ではありませんが、GUID文字列では有効です
プレストンギロット

2
そして、このコードは、入力GUID文字列にこれらの非16進文字が含まれている場合に完全にうまく機能します
rupello

1
  • リフレクターを入手する
  • コピーアンドペーストのGuidの.ctor(String)
  • 「throw new ...」が出現するたびに「return false」に置き換えます。

Guidのctorはほぼコンパイル済みの正規表現です。これにより、例外のオーバーヘッドなしでまったく同じ動作が得られます。

  1. これはリバースエンジニアリングを構成しますか?そうだと思うし、違法かもしれない。
  2. GUIDフォームが変更されると壊れます。

さらにクールな解決策は、「throw new」をオンザフライで置き換えることにより、メソッドを動的に計測することです。


1
私はctorからコードを盗みましたが、サポート作業を実行するために多くの内部プライベートクラスを参照しています。私を信じて、それは私の最初の試みでした。
Ian Boyd、

1

上記のJonまたは同様のソリューション(IsProbablyGuid)によって投稿されたGuidTryParseリンクに投票します。私は自分の変換ライブラリーのようなものを書きます。

この質問が非常に複雑でなければならないのは完全に不自由だと思います。「is」または「as」キーワードは、Guidがnullになる可能性がある場合は問題ありません。しかし、何らかの理由で、SQL Serverはそれで問題ありませんが、.NETはそうではありません。どうして?Guid.Emptyの価値は何ですか?これは.NETの設計によって作成されたばかげた問題であり、言語の慣習がそれ自体を踏むときに本当に私を悩ませます。フレームワークがそれを適切に処理しないので、これまでのところ最高のパフォーマンスの答えはCOM Interopを使用していますか?「この文字列をGUIDにすることはできますか?」答えやすい質問でなければなりません。

アプリがインターネットに接続されるまで、スローされた例外に依存することは問題ありません。その時点で、サービス拒否攻撃を仕掛けるだけです。「攻撃」されない場合でも、一部のyahooがURLを悪用するか、またはマーケティング部門が不正なリンクを送信し、アプリケーションがかなりのパフォーマンスを発揮して、COULDがもたらす発生しない問題を処理するためのコードを記述しなかったため、サーバーをダウンさせました。

これは「例外」の線を少しぼやけさせます-しかし、問題がまれであっても、アプリケーションがクラッシュしてすべてのキャッチにサービスを提供する短い期間で十分な回数発生する可能性がある場合、例外をスローすると思います悪い形。

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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.