ここで回答を確認し、少し遊んだ後、次の実装を思いつきました。これは、コンパイル時ではなく実行時に制約をチェックします。
// This example takes 3 parameters...
public class GenericConstraint<T1, T2, T3>
{
public GenericConstraint(Type type)
{
if (!(type is T1) || !(type is T2) || !(type is T3))
{
throw new Exception("This is not a supported type");
}
}
}
これをカスタムクラスから継承します...
public class Custom<T> : GenericConstraint<string, int, byte>
{
public Custom() : base(typeof(T))
{
}
}
これでエラーがスローされます。
Custom<long> item = new Custom<long>();
これはしません!
Custom<byte> item2 = new Custom<byte>();
Marc Gravellが述べたように、これは継承やジェネリックの適切な使用法ではありません。これを論理的に考えると、GenericConstraintを継承することで、継承がこれだけに制限され、型階層が適切に使用されなくなります。ジェネリックの使用に関しては、これは実際にはかなり無意味です!
したがって、実行時に型を制約するヘルパーメソッドとして機能する別のソリューションがあります。これにより、オブジェクトが継承から解放されるため、型階層には影響しません。
public static void ConstrainParameterType(Type parameterType, GenericConstraint constraintType, params Type[] allowedTypes)
{
if (constraintType == GenericConstraint.ExactType)
{
if (!allowedTypes.Contains<Type>(parameterType))
{
throw new Exception("A runtime constraint disallows use of type " + parameterType.Name + " with this parameter.");
}
}
else
{
foreach (Type constraint in allowedTypes)
{
if (!constraint.IsAssignableFrom(parameterType))
{
throw new Exception("A runtime constraint disallows use of type " + parameterType.Name + " with this parameter.");
}
}
}
}
public enum GenericConstraint
{
/// <summary>
/// The type must be exact.
/// </summary>
ExactType,
/// <summary>
/// The type must be assignable.
/// </summary>
AssignableType
}
これにより、型がシールされている場合などでも、ジェネリックオブジェクトに複数の型制約を適用できるようになりました。
「publicclassCustomwhere T:string ...は許可されていません。これは、それに一致するTがstring(文字列は封印されている)のみであるためです。これにより、ジェネリックとしては無意味になります。」
はい、これは無意味ですが、状況によっては、たとえば、許可するようにオブジェクトを制約したい場合があります。String、StringBuilder、SecureString。これはコンパイル時の制約を提供しませんが、実行時の制約を提供し、制約で使用できる型にある程度の柔軟性を提供します。