79089797

Date: 2024-10-15 11:59:36
Score: 3
Natty:
Report link

An alternative to nullable in the form of a string type for the property (parameter-argument) has already been proposed here. I want to suggest a method that is considered more convenient (using the example of nullable attribute constructor arguments). We have such an attribute (in simplified form), which has 4 nullable constructor-parameters, which are the logic of three states of the bool check field:

class MyAttribute: Attribute
{
public MyAttribute(bool? a=null, bool? b=null, bool? c=null, bool? d=null) :base() { }
}

We can define this constructor in the attribute class, but we cannot use it as an attribute:

[My(null,true,d:false)] // There will be a compilation error
class Another {}

Let's make this constructor private (so as not to interfere –but we will use it) and declare a new one in such a "hack":

class MyAttribute: Attribute
{
public MyAttribute(object? a=null, object? b=null, object? c=null, object? d=null) :this((bool?)a, (bool?)a, (bool?)b, (bool?)d) { } //Here "object?" Nullable as needed (depending on the nullable setting for object types
private MyAttribute(bool? a=null, bool? b=null, bool? c=null, bool? d=null) :base() { }
}

We using nullable object type for the parameters without nullable “bool?”. And using implicit value boxing. And everything will work:

[My(null,true,d:false)] // No compiletime errors
class Another {}

There are several problems left - the first: we do not control the type of arguments that are passed to this constructor. And in our case, an explicit cast of the type - which will give an exception - if the type does not match - this is a mess!

Unfortunately, C # cannot be set to some extended compiletime contract (or in-design; a pity) to limit the actually transmitted argument type. You can manually implement a runtime contract - but what to do if it is violated - do not throw an exception? I suggest just applying your type conversion. Unfortunately, C # does not know how to override type cast operators (which is a pity). But you can create an extension method, for example, of the following type:

public static class ObjectBoolExtension
{
public static bool? ToBool(this object o)
{
    switch (o)
    {
        case false:
        case 0: return false;
        case true:
        case 1: return true;
        default: return null;
    }

}
}

Then the class will be like this

class MyAttribute: Attribute
{
public MyAttribute(object? a=null, object? b=null, object? c=null, object? d=null) :this(a.ToBool(), b.ToBool(), c.ToBool(), d.ToBool()) { }
private MyAttribute(bool? a=null, bool? b=null, bool? c=null, bool? d=null) :base() { }
}
[My(“32”,true,d:1,c:-100)] // No runtime exceptions
class Another {}

If desired, you can add diagnostics:

public static class ObjectBoolExtension
{
        public static bool? ToBool(this object o,
            [System.Runtime.CompilerServices.CallerMemberName] string methodName = "", 
            [System.Runtime.CompilerServices.CallerFilePath] string filepath = "", 
            [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0
            )
        {
            switch (o)
            {
                case false:
                case 0: return false;
                case true:
                case 1: return true;
                case -1:
                case null:
                    return null;
                default:
                    {
#if DEBUG
                        System.Diagnostics.Trace.WriteLine($"Not supported value type {o.GetType().FullName} and value \"{o.ToString()}\" from {methodName} in [{lineNumber}] {filepath}");
#endif
                        return null;
                    }
            }

        }
}

class MyAttribute: Attribute
{
public MyAttribute(object? a=null, object? b=null, object? c=null, object? d=null) :this(a.ToBool(nameof(MyAttribute)), b.ToBool(nameof(MyAttribute)), c.ToBool(nameof(MyAttribute)), d.ToBool(nameof(MyAttribute))) { }
private MyAttribute(bool? a=null, bool? b=null, bool? c=null, bool? d=null) :base() { }
}

But, unfortunately, trace messages appear only when this constructor is explicitly called. That I don't know how to improve. A interesting solution could be to use Roslyn processing of the code syntax - at least CodeAnalysis - such an analyzer could produce correct diagnostics. Similarly, CodeFix could allocate error locations and offer fixes on the fly. This is already the topic of implementing compiletime and in-design contracts - which goes beyond the scope of this subge.

Another problem with using the object type in the constructor-parameter is the use of reflection to create an instance of such an attribute. There may be difficulties in choosing the desired type of parameter - especially when the original values of the arguments are string (but true values of the correct literal type are needed)! I will also leave this out of this discussion.

Please don't scold me too much. This is my first post on stackoverflow ;-)

Reasons:
  • Blacklisted phrase (1): what to do if
  • Blacklisted phrase (1): stackoverflow
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • Low reputation (1):
Posted by: Darklight