I want to write a generic extension method that throws an error if there is no value. SO I want something like:
public static T GetValueOrThrow(this T? candidate) where T : class
{
if (candidate.HasValue == false)
{
throw new ArgumentNullException(nameof(candidate));
}
return candidate.Value;
}
C# is not recognizing the T: The type or namespace name "T" cannot be found
C# is not recognizing where : Constraints are not allowed on non generic declarations
Any idea if this works? What am I missing?
I also come up with:
public static T GetValueOrThrow<T>(this T? candidate) where T : class
{
if (candidate.HasValue == false)
{
throw new ArgumentNullException(nameof(candidate));
}
return candidate.Value;
}
Now C# complains about the candidate: The type T must be a non nullable value type in order to use it as parameter T in the generic type or method Nullable
This is not related to comparing.
public static T GetValueOrThrow<T>(this Nullable<T> candidate) where T : struct // can be this T? as well, but I think with explicit type is easier to understand
{
if (candidate.HasValue == false)
{
throw new ArgumentNullException(nameof(candidate));
}
return candidate.Value;
}
where T : class constrains to reference types, which can be null, but HasValue is property of Nullable type(which is value type as well as T).
Related
I wrote a class called Enumerable that implements IEnumerable<T> and IEnumerator<T> using a lambda function as an alternative to using a generator. The function should return null to indicate the end of the sequence. It allows me to write code such as the following:
static class Program
{
static IEnumerable<int> GetPrimes()
{
var i = 0;
var primes = new int[] { 2, 3, 5, 7, 11, 13 };
return new Enumerable<int>(() => i < primes.Length ? primes[i++] : null);
}
static void Main(string[] args)
{
foreach (var prime in GetPrimes())
{
Console.WriteLine(prime);
}
}
}
Here is the implementation of this class (with some of the code snipped out for brevity and clarity):
class Enumerable<T> : IEnumerable<T>, IEnumerator<T> where T : struct
{
public IEnumerator<T> GetEnumerator()
{
return this;
}
public T Current { get; private set; }
public bool MoveNext()
{
T? current = _next();
if (current != null)
{
Current = (T)current;
}
return current != null;
}
public Enumerable(Func<T?> next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}
private readonly Func<T?> _next;
}
Now, here is the problem: when I change the constraint where T : struct to where T : notnull to also support reference types I get the following errors on the last line of the method GetPrimes:
error CS0037: Cannot convert null to 'int' because it is a non-nullable value type
error CS1662: Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type
It seems that the Enumerable constructor expects a Func<int> rather than a Func<int?> as I coded it:
I don't understand why, if T is int, T? is also treated as int rather than int?. The documentation says that this is the expected behavior for unconstrained type parameters but what's the point of having the notnull constraint if it doesn't allow me to treat T and T? as two different things?
To answer your actual final question - "what's the point of having the notnull constraint ..." - the notnull constraint is part of the nullable context feature, which generally only produces compiler warnings, but (as far as I know) has no effect on the actual compiled program.
Within a nullable-enabled context, the following class will generate a compiler warning "Dereference of a possibly null reference" at c.ToString():
class A<T>
{
public static string? B(T c)
{
return c.ToString();
}
}
If you add the constraint where T : notnull to the class, that warning will disappear. Thus, the constraint allows you to keep better track of possible null reference errors.
This question already has answers here:
Nullable reference types: How to specify "T?" type without constraining to class or struct
(3 answers)
T?, nullable type parameter in C# 9
(2 answers)
Closed 8 months ago.
I have the following struct:
struct Generic<T>
{
public T Property { get; init; }
public Generic(T property) { Property = property; }
public static implicit operator T?(Generic<T> x)
=> x.GetHashCode() == 42 ? null : x.Property;
}
The compiler underlines the null in the implicit conversion and reports error CS0403:
Cannot convert null to type parameter T because it could be a non-nullable value type. Consider using default(T) instead.
But I do not define a conversion to T, I define it to T?! Why does the compiler not see this? And what can I do to get rid of the compiler error?
Strangely, these two nongeneric versions - one with T being a struct, one with T being a class - do not give any compiler errors:
struct NongenericStruct
{
public int Property { get; init; }
public NongenericStruct(int property) { Property = property; }
public static implicit operator int?(NongenericStruct x)
=> x.GetHashCode() == 42 ? null : x.Property;
}
struct NongenericClass
{
public string Property { get; init; }
public NongenericClass(string property) { Property = property; }
public static implicit operator string?(NongenericClass x)
=> x.GetHashCode() == 42 ? null : x.Property;
}
A nullable reference type is not the same as a nullable value type.
As the generic type T has no constraint, the compiler cannot explicitly convert null into either a Nullable<T> (struct) or a T? (class).
In order to fix this you have to explicitly tell the compiler if it's a reference or a value type with a generic type constraint.
// reference type
where T : class
// value type
where T : struct
According to https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/nullable-reference-types-specification.md object is now a valid type constraint.
Nullable reference types can be used as generic constraints. Furthermore object is now valid as an explicit constraint. Absence of a constraint is now equivalent to an object? constraint (instead of object), but (unlike object before) object? is not prohibited as an explicit constraint.
I would think that this should compile correctly but Visual Studio will not let me compile this.
public class A<T> where T : object { }
Am I misreading this or is this an issue with VS or C#8?
UPDATE
I am trying to define an interface for a cache.
public interface IDataCache
{
void Put<T>(string key, T value) where T : notnull;
T? Get<T>(string key) where T : notnull;
}
Ideally, I would not want a null value to be cached but I also cannot guarantee a value is going to be in the cache. Sometimes I want to cache a class but other times it might be an integer. Is this possible to achieve or do I have to make separate method for each case?
public interface IDataCache
{
void Put<T>(string key, T value) where T : notnull;
// This seems really nasty!
T? GetClass<T>(string key) where T : class;
T? GetStruct<T>(string key) where T : struct;
}
This is a valid question and the answer is not trivial. Things have changed a lot since the initial proposal, for valid reasons. Using the object? syntax would lead to confusion in other places.
The link points to the proposal document, not the actual documentation or specification. You'll find the documentation in the C# Guide's Nullable Reference Types. There's no referecence to generic constraints there. The Constraints on type parameters article doesn't mention object? either, but it does mention the notnull constaint.
The changes and the reasons behind them are explained in Try out Nullable Reference Types.
The common case: Nulls allowed
This :
public class A<T>
{
T DoStuff(T input)
{
return input;
}
}
Accepts any struct or value type, including null types. The following line doesn't generate any warnings :
var x=new A<string?>();
notnull constraint
You have to specify that you want non-nullable types with the notnull constraint :
public class A<T>
where T:notnull
{
T DoStuff(T input)
{
return input;
}
}
Using string? as a type parameter creates a warning now:
warning CS8714: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'A'. Nullability of type argument 'string?' doesn't match 'notnull' constraint.
The problem with T?
To use nullable types you have to specify whether the type is a class or struct. The reason for this is explained in The issue with T? section of the blog post that introduced NRTs. T? implies that T is non-nullable so what is T? Class or Struct? The compiler handles each case differently. With a struct, the compiler will generate a Nullable<T> type while classes are handled by compiler magic.
This code :
public class A<T>
{
T? DoStuff(T input)
{
return input;
}
}
Will throw a compiler error, not just a warning :
A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
Adding the class constraint and passing string as the type parameter doesn't generate any errors or warnings :
public class A<T>
where T:class
{
T? DoStuff(T input)
{
return input;
}
}
var x=new A<string>();
I need to test if a value is an instance of a generic base class, without knowing the generic type parameter. Using the MSDN example as the base of my example, this is what I'd like to accomplish:
using System;
public class Class1<T> { }
public class DerivedC1 : Class1<int> { }
class IsSubclassTest
{
public static void Main()
{
Console.WriteLine(
"DerivedC1 subclass of Class1: {0}",
typeof(DerivedC1).IsSubclassOf(typeof(Class1<>)) // <- Here.
);
}
}
While this is syntactically correct, it always yields false. If I remove the generic type parameter, it works as expected (returns true).
How can I test if a class type is a subclass of a generic base class, without knowing its generic type parameter as such?
The problem is that DrevidedC1 is not a sublcass of Class1<T>, it's a subclass of Class1<int>. Make sure you understand this subtle diference; Class1<T> is a open type (T can be anything, it hasn't been set) while DerivedC1 extends a closed type Class1<int> (it's not open in T anymore, T is set to int and only int). So when you do the following:
typeof(DerivedC1).IsSubclassOf(typeof(Class1<>))
The answer is evidently false.
What you need to do is check if the generic type definition of DerivedC1's base type (think of it as the corresponding open generic type of Class1<int>) equals Class1<T> which it clearly does.
The correct code is therefore:
typeof(DerivedC1).BaseType.GetGenericTypeDefinition() == typeof(Class1<>));
Or better yet, as MatÃas Fidemraizer states in his answer:
typeof(DerivedC1).BaseType.GetGenericTypeDefinition().IsAssignableFrom(typeof(Class1<>)));
There's special methods on Type for this sort of thing. As far as I can see, you'll need to walk up your base-types and check each in turn until you either (a) hit a match or (b) get to the top of the inheritance hierarchy (i.e. System.Object).
As such, the following (recursive) extension method:
public static class TypeExtensions
{
public static bool IsDerivedFromGenericParent(this Type type, Type parentType)
{
if(!parentType.IsGenericType)
{
throw new ArgumentException("type must be generic", "parentType");
}
if(type == null || type == typeof(object))
{
return false;
}
if(type.IsGenericType && type.GetGenericTypeDefinition() == parentType)
{
return true;
}
return type.BaseType.IsDerivedFromGenericParent(parentType)
|| type.GetInterfaces().Any(t=>t.IsDerivedFromGenericParent(parentType));
}
}
will allow you to do the following
typeof(DerivedC1).IsDerivedFromGenericParent(typeof(Class1<>))
...and will also work if you test something derived from DerivedC1.
Changing typeof(DerivedC1).IsSubclassOf(typeof(Class1<>)) to typeof(Class1<>).IsAssignableFrom(typeof(DerivedC1).BaseType.GetGenericTypeDefinition()) should be enough in your case.
Type.IsAssignableFrom is more powerful than using Type.IsSubClassOf because it just checks if some type is assignable to other type. This includes, the same type, interface types and other cases.
In C#, the Nullable<T> type does not satisfy the where struct generic constraint (while AFAK this is techically a struct). This can be used to specify that the generic argument has to be a non-nullable value type :
T DoSomething<T>() where T : struct
{
//...
}
DoSomething<int?>(); //not ok
DoSomething<int>(); //ok
And of course, Nullable<T> also does not satisfy the reference type where class constraint :
T DoSomething<T>() where T : class
{
//...
}
DoSomething<int?>(); //not ok
DoSomething<Foo>(); //ok
Is this possible to define a constraint such as it has to be a reference type or a value type but not a Nullable value type ?
Something like this :
void DoSomething<T>() where T : class, struct //wont compile!
{
//...
}
DoSomething<int?>(); //not ok
DoSomething<int>(); //ok
DoSomething<Foo>(); //ok
As noted in a comment, you can do this with overloads and parameters (which can be optional). I blogged about this a while ago, but in your case you'd want:
public class ClassConstraint<T> where T : class
{
}
public class SomeClass<TViewModel>
{
public void Add<TValue>(Func<TViewModel, TValue> expression,
ClassConstraint<TValue> ignored = null)
where TValue : class
{
AddImpl(expression);
}
public void Add<TValue>(Func<TViewModel, TValue> expression,
Nullable<TValue> ignored = null)
where TValue : struct
{
AddImpl(expression);
}
// No constraints
private void AddImpl<TValue>(Func<TViewModel, TValue> expression)
{
...
}
}
It's ugly, but it works:
var z = new SomeClass<string>();
z.Add(x => x.Length); // Valid (non-nullable value type)
z.Add(x => x); // Valid (reference type)
z.Add(x => new DateTime?()); // Invalid (nullable value type)
No, it's not possible on the declaration side. It's either struct OR class.
However, you can check the typeof(T) at run-time to ensure T is Nullable<T2>
Type type = typeof(T);
if(Nullable.GetUnderlyingType(type) == null)
throw new Exception();