いくつかの説明。これらのデータクラスは、プリミティブタイプの1レベル上に設定し、データを制約して意味のあるものにします。
RegexConstrainedString
実際、これらはandクラスのすぐ上にありConstrainedNumber<T>
ます。ここで、コンストラクターの検証コードを別のメソッドにリファクタリングすることについて話し合っています。
検証コードのリファクタリングの問題は、検証に必要な正規表現がのサブクラスにのみ存在することです。RegexConstrainedString
これは、各サブクラスに異なるが含まれているためRegex
です。これは、検証データがRegexConstrainedString
のコンストラクターでのみ使用可能であり、そのメソッドでは使用できないことを意味します。したがって、検証コードを除外すると、呼び出し元はにアクセスする必要がありますRegex
。
public class Password: RegexConstrainedString
{
internal static readonly Regex regex = CreateRegex_CS_SL_EC( @"^[\w!""#\$%&'\(\)\*\+,-\./:;<=>\?@\[\\\]\^_`{}~]{3,20}$" );
public Password( string value ): base( value.TrimEnd(), regex ) {} //length enforced by regex, so no min/max values specified
public Password( Password original ): base( original ) {}
public static explicit operator Password( string value ) {return new Password( value );}
}
したがって、データベースから値を読み取るとき、またはユーザー入力を読み取るとき、PasswordコンストラクターRegex
は検証を処理するためにを基本クラスに転送します。もう1つのトリックは、データベースタイプがvarcharではなくcharの場合に、終了文字を自動的にトリミングすることです。そのため、覚えておく必要はありません。とにかく、RegexConstrainedStringのメインコンストラクターは次のようになります。
protected RegexConstrainedString( string value, Regex subclasses_static_regex, int? min_length, int? max_length )
{
_value = (value ?? String.Empty);
if (min_length != null)
if (_value.Length < min_length)
throw new Exception( "Value doesn't meet minimum length of " + min_length + " characters." );
if (max_length != null)
if (_value.Length > max_length)
throw new Exception( "Value exceeds maximum length of " + max_length + " characters." );
value_match = subclasses_static_regex.Match( _value ); //Match.Synchronized( subclasses_static_regex.Match( _value ) );
if (!value_match.Success)
throw new Exception( "Invalid value specified (" + _value + "). \nValue must match regex:" + subclasses_static_regex.ToString() );
}
呼び出し元はサブクラスにアクセスする必要があるため、Regex
私の最善の策は、サブクラスにメソッドを実装することです。これにより、データが基本クラスのメソッドにIsValueValid
転送されます。つまり、次の行をPasswordクラスに追加します。IsValueValid
RegexConstrainedString
public static bool IsValueValid( string value ) {return IsValueValid( value.TrimEnd(), regex, min_length, max_length );}
ただし、これは好きではありません。サブクラスのコンストラクターコードを複製しているため、文字列を再度トリミングし、必要に応じて同じ最小/最大長を渡すことを忘れないでください。この要件は、RegexConstrainedStringのすべてのサブクラスに強制されますが、これは私がやりたいことではありません。パスワードのようなこれらのデータクラスは非常に単純です。これは、RegexConstrainedStringがほとんどの作業を処理し、演算子、比較、クローン作成などを実装するためです。
さらに、コードを除外することには他にも複雑な問題があります。一部のデータ型には文字列の特定の要素についてレポートするプロパティがある場合があるため、検証には、インスタンスでの正規表現の一致の実行と保存が含まれます。たとえば、私のSessionIDクラスには、データクラスインスタンスに格納されているMatchから一致したグループを返すTimeStampなどのプロパティが含まれています。肝心なのは、この静的メソッドはまったく異なるコンテキストであるということです。本質的にコンストラクターコンテキストと互換性がないため、コンストラクターはそれを使用できません。そのため、コードをもう一度複製することになります。
つまり...検証コードを複製して静的コンテキストに合わせて微調整し、サブクラスに要件を課すことで検証コードを除外することも、はるかに単純にしてオブジェクトの構築を実行することもできます。文字列とMatch参照のみがインスタンスに格納されるため、割り当てられる相対的な余分なメモリは最小限になります。一致や文字列自体など、他のすべてはとにかく検証によって生成されるため、それを回避する方法はありません。 私は一日中パフォーマンスを心配することができましたが、私の経験はその正しさでした正確さは他の多くの最適化につながることが多いため、より重要です。たとえば、アプリケーションを流れる不適切なフォーマットやサイズのデータについて心配する必要はありません。意味のあるデータ型のみが使用されるため、データベースであるかどうかにかかわらず、他の層からアプリケーションへのエントリポイントへの検証が強制されます。またはUI。私の検証コードの99%は、不要になったアーティファクトとして削除されました。最近では、nullをチェックするだけです。ちなみに、この時点に達したので、ヌルを含めることが何十億ドルの間違いだったのかがわかりました。それらは私のシステムには本質的に存在していませんが、私がもうチェックしなければならないのはそれだけのようです。これらのデータ型をフィールドとして持つ複雑なオブジェクトはnullで構築できませんが、プロパティセッターでそれを強制する必要があります。そうしないと、検証コードが必要になることはないため、イライラします...値の変更に応答して実行されるコードのみ。
更新:CLR関数呼び出しを双方向でシミュレートしたところ、すべてのデータが有効な場合、パフォーマンスの違いは1000回の呼び出しあたり1ミリ秒のほんの一部であり、無視できることがわかりました。ただし、パスワードの約半分が無効であり、「インスタンス化」バージョンで例外がスローされると、3桁遅くなります。これは、1000回の呼び出しごとに約1秒余分に相当します。もちろん、テーブル内の複数の列に対して複数のCLR呼び出しが行われるため、違いの大きさは複数になりますが、私のプロジェクトでは3〜5倍になります。それで、コードを非常にシンプルでクリーンに保つためのトレードオフとして、1000回の更新ごとに3〜5秒余分に受け入れることができますか? それは更新レートに依存します。私のアプリケーションが1秒あたり1000の更新を取得している場合、3〜5秒の遅延は壊滅的です。一方、1分または1時間に1000回の更新を取得していた場合は、完全に許容できる可能性があります。 私の状況では、それはかなり受け入れられると言えるので、インスタンス化メソッドを使用して、エラーを通過させると思います。もちろん、このテストでは、SQL Serverにエラーを処理させるのではなく、CLRでエラーを処理しました。エラー情報をSQLServerにマーシャリングしてから、場合によってはアプリケーションに戻すと、処理速度が大幅に低下する可能性があります。実際のテストを行うには、これを完全に実装する必要があると思いますが、この予備テストから、結果がどうなるかはかなり確信しています。