Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configuration String with Null DefaultValue

I have the following ConfigurationProperty as part of an element:

[ConfigurationProperty("example", IsRequired = false, DefaultValue = null)]
public string Example { 
    get { return (string)base["example"]; }
    set { base["example"] = value; }
}

If I set it as follows, it takes on the "Hello" string and works properly:

<myElement example="Hello"/>

If it is not present, I run into problems:

<myElement/>

Instead of taking on the default value of null as specified above, it takes on String.Empty. Why is that, and how can I get it to take on a default value of null?

Update

Its definitely because base["example"] returns String.Empty, where base is a ConfigurationElement (the indexer is defined here: https://msdn.microsoft.com/en-us/library/c8693ks1(v=vs.110).aspx), but I am still not sure why it doesn't take on a value of null.

Update

Even DefaultValue = default(string) sets the string to String.Empty.

Update

Even base.Properties.Contains("example") returns true if the property does not exist in the configuration.

like image 327
Alexandru Avatar asked Apr 22 '15 13:04

Alexandru


People also ask

Is string null by default C#?

The object and string types have a default value of null, representing a null reference that literally is one that does not refer to any object.

IS NULL a string type?

The Java programming language distinguishes between null and empty strings. An empty string is a string instance of zero length, whereas a null string has no value at all.

What is the default value of null?

The value produced by setting all value-type fields to their default values and all reference-type fields to null . An instance for which the HasValue property is false and the Value property is undefined. That default value is also known as the null value of a nullable value type.


3 Answers

Judging from the Reference Source for the ConfigurationProperty class, this is perhaps not a bug, but a feature.

Here is the relevant internal method, InitDefaultValueFromTypeInfo (with some minor formatting modifications by me):

private void InitDefaultValueFromTypeInfo(ConfigurationPropertyAttribute attribProperty,
                                          DefaultValueAttribute attribStdDefault) {
     object defaultValue = attribProperty.DefaultValue;

     // If there is no default value there - try the other attribute ( the clr standard one )
     if ((defaultValue == null || defaultValue == ConfigurationElement.s_nullPropertyValue) &&
         (attribStdDefault != null)) {
         defaultValue = attribStdDefault.Value;
     }

     // If there was a default value in the prop attribute - check if we need to convert it from string
     if ((defaultValue != null) && (defaultValue is string) && (_type != typeof(string))) {
         // Use the converter to parse this property default value
         try {
             defaultValue = Converter.ConvertFromInvariantString((string)defaultValue);
         }
         catch (Exception ex) {
             throw new ConfigurationErrorsException(SR.GetString(SR.Default_value_conversion_error_from_string, _name, ex.Message));
         }
     }

     if (defaultValue == null || defaultValue == ConfigurationElement.s_nullPropertyValue) {
         if (_type == typeof(string)) {
             defaultValue = String.Empty;
         }
         else if (_type.IsValueType) {
             defaultValue = TypeUtil.CreateInstanceWithReflectionPermission(_type);
         }
     }

     SetDefaultValue(defaultValue);
 }

The last if block is interesting: If your property has type string, and the default value is null, then the default value gets changed to string.Empty.

The first if block hints at a possible explanation for this peculiar behaviour. The [ConfigurationProperty] attribute's DefaultValue property is optional. If DefaultValue is not set by the programmer, then it defaults null itself. The first if block uses that default null to check whether a DefaultValue was specified. If not, it falls back to fetching a default value from a [DefaultValue] attribute, if such a one present.

This all means: Specifying DefaultValue = null has the same effect as not specifying it at all, and in that case, the configuration sub-system chooses a "sane" default value for strings: the empty string.

Workaround:

Here is a somewhat hacky workaround: Don't declare your configuration property as string, but as a thin wrapper type around string; then declare a suitable type converter:

[ConfigurationProperty("name", IsRequired = false)]
[TypeConverter(typeof(IncognitoStringConverter))]  // note: additional attribute!
public IncognitoString Name                        // note: different property type
{
    get
    {
        return (IncognitoString)base["name"];
    }
    set
    {
        base["name"] = value;
    }
}

Here are the implementations for IncognitoString and IncognitoStringConverter:

public struct IncognitoString
{
    private IncognitoString(string value)
    {
        this.value = value;
    }

    private readonly string value;

    public static implicit operator IncognitoString(string value)
    {
        return new IncognitoString(value);
    }

    public static implicit operator string(IncognitoString incognitoString)
    {
        return incognitoString.value;
    }

    … // perhaps override ToString, GetHashCode, and Equals as well.
}

public sealed class IncognitoStringConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        return (IncognitoString)(string)value;
    }
}

Because IncognitoString is implicitly convertible to string, you can assign the property value to any string variable. I know, it's hacky and really complicated just to get nullable properties. Perhaps just live with the empty string.

like image 184
stakx - no longer contributing Avatar answered Oct 11 '22 01:10

stakx - no longer contributing


Another workaround is to make a call something like this:

[ConfigurationProperty("Prompt")]
public string Prompt
{
    get { return this.GetNullableStringValue("Prompt"); }
}

private string GetNullableStringValue(string propertyName)
{
    return (string)this[new ConfigurationProperty(propertyName, typeof(string), null)];
}

Calling GetNullableString like this bypasses the configuration property attribute and stops it defaulting the DefaultValue to null. You could put the method in a base class too to make it a bit tidier.

You just have to remember that you are calling it if you want to change the default.

You could also put in a call to this.ElementInformation.Properties[propertyName] if you wanted to lift some of the other things you might define on the attribute - just don't use that to populate DefaultValue

like image 25
JaySeeAre Avatar answered Oct 11 '22 01:10

JaySeeAre


Instead of checking the property value for null you can easily check if the property has been set in the configuration file or the default value has been returned. This can be done be looking at the ValueOrigin in the ConfigurationElement's ElementInformation.

// if not the default value...    
if (MyConfigurationElement.ElementInformation.Properties["example"].ValueOrigin!=
        PropertyValueOrigin.Default)
{
    ...
}

See also the documentation of the values of the PropertyValueOrigin Enumeration.

like image 29
Daniel Calliess Avatar answered Oct 11 '22 01:10

Daniel Calliess