Say you have a generic class Foo
:
public class Foo<T> {
public T Data {
get;
protected set;
}
}
Is it possible to define a constructor that is only applicable if T
inherits (or is) a specific type.
For instance say T
is an int
:
public Foo () {
this.Data = 42;
}
The type constraint should be checked at compile-time. This might be useful for optimization. Say for instance you have an IEnumerable<T>
and you wish to make a "cache" (since LINQ queries can be quite expensive). Now if the IEnumerable<T>
is already an IList<T>
, it is useful not to copy data. On the other hand if it is really a LINQ query, another constructor can store the data in an array.
As a workaround, one can of course inherit Foo
(e.g. IntFoo
) and define a constructor there:
public class IntFoo : Foo<int> {
public IntFoo () {
this.Data = 42;
}
}
A problem with this approach is however that private
data is not accessible (or one has to make it protected
). Are there some other disadvantages, or does one is supposed to model type-specific constructors this way?
There's a trick that you could apply here. It's fexible for many scenarios.
internal static class FooHelper
{
private static class DefaultData<T>
{
public static T Value = default(T);
}
static FooHelper()
{
DefaultData<int>.Value = 42;
DefaultData<string>.Value = "Hello World";
}
// From @JeffreyZhao:
//
// Use a static method to trigger the static constructor automatically,
// or we need to use RuntimeHelpers.RunClassConstructor to make sure
// DefaultData is corrected initialized.
//
// The usage of RuntimeHelpers.RunClassConstructor is kept but commented.
// Using GetDefault<T>() is a better approach since static Foo() would be
// called multiple times for different generic arguments (although there's
// no side affect in this case).
//
// Thanks to @mikez for the suggestion.
public static T GetDefault<T>()
{
return DefaultData<T>.Value;
}
}
public class Foo<T>
{
/* See the comments above.
static Foo()
{
RuntimeHelpers.RunClassConstructor(typeof(FooHelper).TypeHandle);
}
*/
public T Data { get; protected set }
public Foo()
{
Data = FooHelper.GetDefault<T>();
}
}
You could specify the default values for limited types, and the result of them would be kept to default values.
This trick has several variations in practice. In my project, we use a generic ITypeConverter<T>
instead of the build in TypeConverter
to avoid unnecessary boxing:
public interface ITypeConverter<T>
{
bool CanConvertTo<TTarget>();
TTarget ConvertTo(T value);
}
The same trick could be applied as:
public class LongConverter : ITypeConverter<long>
{
private static class Op<TTarget>
{
public static Func<long, TTarget> ConvertTo;
}
static LongConverter()
{
Op<string>.ConvertTo = v => v.ToString();
Op<DateTime>.ConvertTo = v => new DateTime(v);
Op<int>.ConvertTo = v => (int)v;
}
public TTarget ConvertTo<TTarget>(T value)
{
return Op<TTarget>.ConvertTo(value);
}
}
Elegant, fast and clean.
public class Foo<T>
{
public T Data
{
get;
protected set;
}
public Foo()
{
switch (Type.GetTypeCode(Data.GetType()))
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
Data = (T)Convert.ChangeType(42, typeof(T));
break;
default:
break;
}
}
}
This way you have a constructor for a lot of types
public class Foo<T>
{
public T Data
{
get;
protected set;
}
public Foo()
{
switch (Type.GetTypeCode(Data.GetType()))
{
case TypeCode.Boolean:
Data = ConvertValue<T>(true);
break;
case TypeCode.DateTime:
Data = ConvertValue<T>("01/01/2014");
break;
case TypeCode.Double:
Data = ConvertValue<T>(0.5);
break;
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
Data = ConvertValue<T>(32);
break;
case TypeCode.String:
Data = ConvertValue<T>("Test");
break;
default:
break;
}
}
private static T ConvertValue<T>(object value)
{
return (T)Convert.ChangeType(value, typeof(T));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With