How to make Object property of a C# class optional? - c#

I am trying to make a model class in C# in which i require object/List properties as optional property:
public class Customer
{
[JsonProperty("Custid")]
public string CustId { get; set; }
[JsonProperty("CustName")]
public string CustName { get; set; }
}
public class Store
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("Name")]
public string? Name { get; set; }
[JsonProperty("Customer")]
public List<Customer>? Customers{ get; set; } *//Error 1*
[JsonProperty("OtherProperty")]
public object? OtherProperty{ get; set; } *//Error 2*
}
The above code is giving error as :-
Error 1: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable'
Error 2: The type 'List' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable'
Please Explain me the above scenario and provide me with the alternate solution.

string, List and object are all reference types. Those are nullable by default. The Nullable type (e.g. int? is a shorthand for Nullable<int>) is only used for value types.
In C# 8.0, a new feature was introduced that allows for non-nullable reference types - i.e. reference types that explicitly disallow null assignment. This is an opt-in feature - you can enable it to allow you to more clearly show intent about the references. If you use this, the syntax used to define nullable reference types is the same as for nullable value types:
string nonNullableString = null; // Error
string? nullableString = null; // Ok
Keep in mind that enabling non-nullable reference types means that all of the reference types that aren't followed by ? will be non-nullable; this might require you to make lots of changes in your application.
So there's your two choices. Either enable non-nullable reference types, and then you need to explicitly mark types that you want to have nullable, or stick with nullable reference types, and just use string instead of string? for the same result. I would encourage the use of non-nullable types by default, since it holds some promise for avoiding an entire class of very common programming mistakes.

If you aren't using C# 8:
object? doesn't exists. object is already nullable.
List<Customer>? doesn't exists. List<Customer> is already nullable.
If you want to use nullable reference types you must update your compiler version!

The Nullable<T> type requires that T is a non-nullable value type, for example int or DateTime. Reference types like string or List can already be null. There would be no point in allowing things like Nullable<List<T>> so it is disallowed.

Related

Why isn't this property being treated as a Nullable<int>?

Given the following class
public class Component<TValue>
{
public TValue? MaxValue { get; set; }
public TValue? MinValue { get; set; }
public TValue Value { get; set; } = default!;
public override string ToString() =>
$"MinValue = {MinValue}, MaxValue = {MaxValue}, Value = {Value}";
}
Why is it that when I create an instance of Component<int> the MinValue and MaxValue properties are int instead of Nullable<int>?
static void Main(string[] args)
{
var instance = new Component<int>();
instance.Value = 42;
Console.WriteLine(instance.ToString());
}
I could get it to behave how I want to by adding where TValue: struct, but I am writing a Blazor component that needs to work with any numeric type (including nullables) so I can't add that constraint.
I'd like to know the logic behind why TValue? for an int should not be compiled as Nullable<Int32>.
You did not constrain your class's type parameter neither to struct nor to class.
When you create new instance of Component with generic type parameter which is not nullable (in your case 'int') all of the properties using this generic type, will become int.
If you want your TValue to be nullable, create instance providing 'int?' as a generic type parameter.
When the type parameter T in syntax T? is not constrained to a struct it takes on the nullable reference type meaning of T?. In the cases where T is instantiated with a reference or interface type then the result will continue to be a nullable reference type. For instance if it's instantiated with string the value is treated as string?. In the case T is instantiated with a value type the ? is effectively dropped as it has no meaning.
If the desire is to have T? mean a nullable value type then T must be constrained to struct
If the desire is to have T? mean Nullable<T> when instantiated with a value type and T? when instiated with a reference type, then unfortunately there is no such syntax for that. It would require significant runtime work to provide

C# TypeKind of string? is TypeKind.Class

Rephrased question:
I have this equalitycomparer with Generic type constrained to a class
public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
{
public static ReferenceEqualityComparer<T> Default => new();
public bool Equals(T? x, T? y) => ReferenceEquals(x, y);
public int GetHashCode(T? obj) => RuntimeHelpers.GetHashCode(obj);
}
when a class let's say
public class A
{
public string? P1{get; set;}
}
is consumed by a code generator
[Generator]
public class MyCodeGenerator : ISourceGenerator
{
}
the NetAnalyzer says that string? has a TypeKind of class
but when I do this,
#nullable enable
[TestMethod]
public void RefTest()
{
string? s1 = "adsad";
string? s3 = s1;
Assert.IsTrue(ReferenceEqualityComparer<string?>.Default.Equals(s1, s3));
}
#nullable restore
it says that string?does not match 'class' constraint. Even though the analyzer is telling me that its a class, am I missing something here? or have I misunderstood the concept?
Original question: According to the description of Nullable from Microsoft Documentation, they are classified as structs, but why is it that the CodeAnalyzer is telling me that the TypeKind of string? is a TypeKind.Class?
Here's some context, in a library I'm writing, classes are analyzed for Source Generation (C# 9 Source Generator) which is essentially using .NetAnalyzer. Each of the properties of the class will be checked whether their type is considered as a class. It turns out string? is considered as a Class.
There is a specific constraint for nullable classes in C#, so from constraint standpoint in compile time SomeRefType? does not match T:class, but it will match T:class?.
where T : class The type argument must be a reference type. This constraint applies also to any class, interface, delegate, or array type. In a nullable context in C# 8.0 or later, T must be a non-nullable reference type.
where T : class? The type argument must be a reference type, either nullable or non-nullable. This constraint applies also to any class, interface, delegate, or array type.
So possibly in nullable context you will want to use the second one:
public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class?
Which will not give any warnings neither for string? nor for string.
strings are classes and cannot be used as generic type argument for Nullable because of the generic constraint where T : struct. In your context string? is a nullable reference type, which can be used to incdicate to the compiler that a reference type should not have null as value (variables still can have null as value and should be checked for null values in public APIs, but the compiler will warn you when you use null in a non-nullable context)

Complex Types of Nullable Values

For a complex type in entity framework with only nullable properties, why is that for something like the following requires the complex type be instantiated:
[ComplexType]
public class Address {
public string Address1 { get; set; }
}
public class Customer {
[Key]
public int CustomerId {get;set;}
public Address Address {get;set;}
}
More specifically, if you don't declare and address type, you get a "Null value for non-nullable member. Member: 'Address'." As per this question.
As long as all the properties in the complex type are nullable, why is it that entity framework requires an instance of Address? Since Address1 is nullable, why can it not just assume that and create the table (and column Address_Address1) and null value in the row as if I had created an instance of Address with a null Address1?
Or is there an attribute/fluent setting I can apply to achieve that?
look at this question Entity Framework 5 DbUpdateException: Null value for non-nullable member.
you need to instantiate the complex type even if all the properties are null.
Even if the properties are nullable, the class containing them isn't. You can have Address.Address1 as null, but Address itself has to be instantiated.

named parameter type constraints

I am designing a custom attribute class.
public class MyAttr: Attribute
{
public ValueRange ValRange { get; set; }
}
Then I am attempting to assign this attribute to a property in an adjoining class:
public class Foo
{
[MyAttr(ValRange= new ValueRange())]
public string Prop { get; set; }
}
However, the compiler is complaining the following:
'ValRange' is not a valid named attribute argument because it is not a valid attribute parameter type
I also tried converting the ValueRange class to a struct in hopes that become a value type might solve the problem. Is there any way around this?
Is there any way around this?
No.
For more details I refer you to section 17.1.3 of the C# 4 specification, which I reproduce here for your convenience:
The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
The type object.
The type System.Type.
An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility.
Single-dimensional arrays of the above types.
A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.
Remember, the point of an attribute is to at compile time add information to the metadata associated with the entity upon which you've placed the attribute. That means that all the information associated with that attribute must have a well-defined, unambiguous way to serialize it into and out of metadata. By restricting the set of legal types to a small subset of all possible types we ensure that the compiler can always emit legal metadata that the consumer can understand.
Attribute parameter values need to be resolvable at compile time (i.e constants).
See Attribute Parameter Types on MSDN:
Values passed to attributes must be known to the compiler at compile time.
If you can create a ValueRange that is a constant, you can use it.
Is there any way around this?
Yes.
You can have your attribute use a Type property and then use types that implement a defined interface, for which the code that processes that attribute would have to assume, and as such also create an implicit, but hopefully documented, requirement to its clients:
public interface IValueRange {
int Start { get; }
int End { get; }
}
public class MyAttr : Attribute {
// The used type must implement IValueRange
public Type ValueRangeType { get; set; }
}
// ....
public class Foo {
class FooValueRange : IValueRange {
public int Start { get { return 10; } }
public int End { get { return 20; } }
}
[MyAttr(ValueRangeType = typeof(FooValueRange))]
public string Prop { get; set; }
}
This is not unlike many classes in the System.ComponentModel namespace, like DesignerAttribute.
Attribute parameters must be values of the following types (quoting the article):
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
One-dimensional arrays of any of the above types
Edit: Changed "compile-time constant" to "value", since types and arrays are not constants (thanks to the commenter who pointed this out (and subsequently deleted his comment for some reason...))
Attributes can only receive compile-time-constants as parameters (e.g. 3, "hello", typeof(MyClass), "path to a resource defining whatever non constant data you need").
The last example (passing a type) I gave may help you design a workaround (pass a type implementing an interface with the method you need).

Detecting if class property is a reference type

Is it possible when looking at a class' properties to detect if any of them is a reference type.
Take below as an example:
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProgrammeClient
{
public int Id { get; set; }
public bool IsActive { get; set; }
public IClient Client { get; set; }
}
ProgrammeClient: -
Id and IsActive are properties but Client is a reference type. Is there a way of detecting this?
Many thanks,
Kohan.
Addendum
The reason i ask is: I am using a mapper that checks types are the same before matching property names and copying the values. My hope is to detect classes and override the type matching and simply copy the classes properties if the THEY type match.
Well, it sounds like you may be trying to detect the difference between a value type and a reference type. You can find that out using Type.IsValueType... but be aware that value types can easily have properties too. (Think about DateTime for example.) Also, some types which you may want to regard as "not objects" are reference types - string being a prime example.
Another option would be to use Type.IsPrimitive - is that what you're looking for? If so, you should be aware that decimal, DateTime and string are not primitive types.
If you can describe exactly what makes a type an "object" in your way of thinking (or rather, in whatever way makes a semantic difference in what you're trying to do with your type). I suspect you don't currently have a very clear set of criteria - coming up with those criteria may well clarify other aspects of your current task, too.
You can use a little reflection to see if a property is a value type or a class type. Class is probably what you mean by "object". All types in .NET derive from the object type.
Client.GetType().IsClass
Or you can loop through all properties and see which are compound
foreach(var p in ProgrammeClient.GetType().GetProperties())
{
if(p.PropertyType.IsClass) Console.WriteLine("Found a class");
}
Check if the type is a string and check if it is a class.
public static bool IsNonStringClass(this Type type)
{
if (type == null || type == typeof(string))
return false;
return typeof(Type).IsClass;
}
All properties in your example return objects, as everything is an object in .NET; int and bool are objects. If you mean a reference type, as opposed to value types, then you can do the following:
foreach (PropertyInfo pi in typeof(Client).GetProperties()) {
if (pi.PropertyType.IsClass) {
// reference type
// DoMyFunkyStuff
}
}
You can enumerate the properties via Reflection, and check them:
bool ContainsOnlyValues() {
return typeof(ProgrammeClient).GetProperties().All(x => x.PropertyType.IsValueType);
}
The Type.IsvalueType property can reveal this.
Id.GetType().IsValueType
This will be True for Id, false for a class
If using TypeSupport nuget package you can simply do:
typeof(ProgrammeClient).GetExtendedType().IsReferenceType;
TypeSupport does inspection and provides deeper insight on the capabilities of a given type, handling things like strings, enums etc and makes it easier to code these types of things.

Categories

Resources