1

Let's say I have an configuration or DTO object with use of C# nullable reference object feature:

public class ServiceConfig
    {
        public string ConnectionString { get; set; }
        public string? OptionalComment { get; set; }
    }

With upper code I get compiler warning "CS8618: Non-nullable property 'ConnectionString' must contain a non-null value when exiting constructor. Consider declaring the property as nullable." Because this objects needs parameterless constructor I can add = null! to get rid of the warning. But the problem is that object can still contain property with null value. Because I want to use this in a context of configuration and DTO's, there is an option to validate objects (in deserialization process) before they are passed to "real" objects.

How can I do it? It there a way where I can validate if object is valid by "Nullable references" notation? I see other option also with Data Annotations, but I use of this C# feature is a lot more attractive.

So, in fantasy I would like to have something like this:

public class ServiceConfig
{
    public string ConnectionString { get; set; } = null!;
    public string? OptionalComment { get; set; }
}

public class Deserializer
{
    public static ServiceConfig Deserialize(string data)
    {
        var result = Json.Deserialize<ServiceConfig>(data);
        var isObjectValid = result.IsValid(); // I want method something like this
        if (!isObjectValid) throw new Exception("Deserialization error");
        return result;
    }
}
Rok
  • 451
  • 3
  • 21
  • 1
    Does this answer your question? [How to use .NET reflection to check for nullable reference type](https://stackoverflow.com/questions/58453972/how-to-use-net-reflection-to-check-for-nullable-reference-type). Basically, you'd check for every property if it's nullable, and if not, whether it then contains `null`. The way NRT annotations are stored is not trivial, and so this check would not be, either. Annotations enable more validations than mere nullability. – Jeroen Mostert Dec 07 '20 at 15:41
  • If you can use Json.Net it should be possible to use a non-empty constructor: https://stackoverflow.com/questions/23017716/json-net-how-to-deserialize-without-using-the-default-constructor – JonasH Dec 07 '20 at 16:00
  • 1
    My suggestion would be to check if a source generator (https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) can be done, which would provide you the IsValid() method – Ramesh Dec 07 '20 at 16:14
  • Have you considered adding a parameterless constructor that sets the value to "" instead of null? – Jon Skeet Dec 07 '20 at 17:31
  • The fundamental problem here is that you've got a single type trying to represent two different states - an unvalidated object (where ConnectionString can be null) and a validated object (where it can't). If you want to be genuinely strict about things, you might want to separate those concepts into two types - one just for deserialization purposes, and then make your Validate method actually validate *and convert* into the stricter type. It does mean a lot of duplication, but it can have various benefits in my experience. – Jon Skeet Dec 07 '20 at 18:34
  • @jon-skeet: I think I get your idea but...I see also a lot of problems in "duplicate" code and because of that more maintenance and more bugs. I like the idea of Ramesh, but code generators are new and I don't know if I can use it in library which targets .NET Framework. – Rok Dec 08 '20 at 08:48
  • I'm not sure that a code generator will really help (while you stick to a single type), because the problem is more fundamental than that. Suppose you have a method accepting a `ServiceConfig` - how do you know whether that's going to be validated or not? If it *is* validated, you should be able to assume that `ConnectionString` won't be null. If it *isn't* validated, you shouldn't assume that. You can document it, of course - but how do you expect the compiler to know the difference? – Jon Skeet Dec 08 '20 at 09:28
  • @jon-skeet: My idea is to have some kind of proxy/factory of those objects. In that case I can trigger validation like this. Otherwise I don't know how to do it even in theory because while deserialization object can be, on some monements, in invalid state. I also like Records for this occasion because those objects normally don't change, but I still have a problem with compatibility in .NET Framework 4.x. – Rok Dec 08 '20 at 09:40
  • @Rok: So is your question *really* just about the reflection aspect? If so, it looks like you're covered by the duplicate. It's fairly unclear what exactly you're asking though. – Jon Skeet Dec 08 '20 at 10:04
  • @jon-skeet: Theoretically I would your first suggestion with two classes. So `Dto` with nullable references, `ValidDto` with constraints and then some logic for validation and mapping. But, pragmatically, I can not imagine how to maintain all this duplicate code. And it looks like i will go with Nuget solution suggested in Ramesh's answer. – Rok Dec 08 '20 at 13:26

2 Answers2

0

As per the docs @ https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references

The compiler doesn't add any null checks or other runtime constructs in a nullable context. At runtime, a nullable reference and a non-nullable reference are equivalent.

So, when a DTO is deserialized at runtime, you may not have any additional constructs to denote whether a field is Nullable reference type or non-nullable reference type at atleast at the time of writing this answer.

Ramesh
  • 13,043
  • 3
  • 52
  • 88
-1

It seems that doesn't need to declare get setter explicitely, so you can declare the public member by any default value, and let the compiler doing its job for you perfectly. :D

public string ConnectionString = null;
OO7
  • 660
  • 4
  • 10
  • Well that's now a field which is rather different from a property, and I'd expect this to give the same error in that you've got a non-nullable field declaration, with a null value on initialization... – Jon Skeet Dec 07 '20 at 17:29
  • I think the problem come from deserialization routine, so the only way is to change property to member. By the way string object is nullable, and by default has null value. – OO7 Dec 07 '20 at 17:45
  • 1
    I think you've missed the point of nullable reference types. And properties are members too - I think you mean field. But I see no indication that there's any problem with deserialization. I believe you've misunderstood the point of the question. – Jon Skeet Dec 07 '20 at 18:14
  • Yes you right, the field member. But are you sure the json deserialization could manipulate the property with default by null value? – OO7 Dec 07 '20 at 18:25
  • 1
    Yes, absolutely. JSON deserialization typically *does* work with properties. The problem isn't the deserialization at all - it's the default value of a non-nullable member being null. – Jon Skeet Dec 07 '20 at 18:33
  • So you means the string object is non nullable? – OO7 Dec 07 '20 at 18:43
  • 1
    I'm not going to try to explain the nullable reference types feature in a comment thread. It's *far* too complex for that. I suggest you read https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references – Jon Skeet Dec 07 '20 at 18:50
  • Okey, right now let me explain about reference manner. You know that any reference pointed to any address on memory which will be kept until it released. And if it has released, it has no valid anymore. So in terms of Serialization, it would produce invalid reference when the released reference been deserialized. – OO7 Dec 08 '20 at 18:19
  • I have no idea what you mean by "And if it has released"... and it's unclear what any of this has to do with the question, either. – Jon Skeet Dec 08 '20 at 19:08
  • (Sorry, that was abrupt than I intended it to be. I just really don't know where you're going with your previous comment. It doesn't sound like it's related to nullable reference types. Please clarify if it really *is* related.) – Jon Skeet Dec 08 '20 at 19:14
  • Oh yeah, really? I think it is basic concept related to all thing about variable types and either class types on the fly. – OO7 Dec 08 '20 at 19:22
  • Sorry, I'm still not following you. I suspect that further discussion wouldn't be helpful - but I also still believe you are probably misunderstanding the question. – Jon Skeet Dec 08 '20 at 19:27
  • simply reference is a pointer. – OO7 Dec 08 '20 at 19:28