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.
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.
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.
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.
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.
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With