public class Foo<T> { internal Foo() { } }
public sealed class Foo_SByte : Foo<SByte> { }
public sealed class Foo_Byte : Foo<Byte> { }
public sealed class Foo_Short : Foo<Int16> { }
public sealed class Foo_UShort : Foo<UInt16> { }
public sealed class Foo_Int : Foo<Int32> { }
public sealed class Foo_UInt : Foo<UInt32> { }
public sealed class Foo_Long : Foo<Int64> { }
public sealed class Foo_ULong : Foo<UInt64> { }
public sealed class Foo_Char : Foo<Char> { }
public sealed class Foo_Float : Foo<Single> { }
public sealed class Foo_Double : Foo<Double> { }
public sealed class Foo_Bool : Foo<Boolean> { }
public sealed class Foo_Decimal : Foo<Decimal> { }
public sealed class Foo_String: Foo<string> { }
public class Foo_Struct<T> : Foo<T> where T : struct { }
public class Foo_Enum<T> : Foo<T> where T : Enum { }
// Now T is immutable
My objective is to restrict use of Foo to all potential value types, which I am semantically (maybe incorrectly) assuming to be equivalent to saying "all immutable types"
In other words, I dont want T to be a reference type.
The implementation doesn't change between Bars
. I only want the guarantee of T restricted to immutable types.
Have I missed any (excluding the simple types, which I am aware of)?
Is there a better way?
Edit: Ok, I fully implemented it. Now all uses of Foo<T>
are guaranteed to be immutable. (If not, please tell me what types I missed. Or try to inherit from a Foo to prove me wrong).
CodePudding user response:
It isn't possible to check for immutability, neither at compile time nor at runtime. The closest you can get is detecting if a type has copy semantics (that is, if the assignment x = y
will result in x
having a value that is independent of y
being changed -- sort of, read on), because the list of types that effectively have (intrinsic) copy semantics is restricted in C#. So you can have a runtime check for that:
class Foo<T> {
public Foo() {
if (!(typeof(T).IsValueType || typeof(T) == typeof(string))) {
throw new ArgumentException($"{typeof(T)} does not have copy semantics.");
}
}
}
Conceptually this check happens every time a Foo<T>
is instantiated, but practically speaking the JIT can optimize it away.
Note that just having copy semantics is still no guarantee of correctness with regards to state. In particular, a user-defined value type could refer to state outside the instance. Simple example:
struct SneakyStruct {
static int i = 0;
public int Value => i ;
}
The fact that you can copy SneakyStruct
and there's no way to modify the state from the outside is still of no help in guaranteeing you'll get the same Value
every time. Of course this example is deliberately perverse, but still -- care should be taken not to rely on things you don't have to rely on, or actually can't rely on.