具有空DefaultValue的配置字符串

15

我有以下的ConfigurationProperty作为一个元素的一部分:

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

如果我按照以下方式设置它,它会采用"Hello"字符串并正常工作:

<myElement example="Hello"/>
如果它不存在,我会遇到问题:
<myElement/>

与上面指定的默认值null不同,它采用了String.Empty。为什么会这样,我该如何使其采用null的默认值?

更新

它绝对是因为base["example"]返回String.Empty,其中base是一个ConfigurationElement(索引器在此处定义:https://msdn.microsoft.com/en-us/library/c8693ks1(v=vs.110).aspx),但我仍然不确定为什么它不能采用null的值。

更新

即使DefaultValue=default(string)也会将字符串设置为String.Empty

更新

甚至base.Properties.Contains("example")如果属性不存在于配置中,则返回true


2
我不知道为什么 DefaultValue 没有被使用,但你是否尝试通过扩展属性的 getter 方法并检查 base.Properties.Contains("example"),如果为 false 则手动返回 null - stakx - no longer contributing
@stakx 感谢您的建议。你不会相信,即使在配置中不存在一个字符串属性时,base.Properties.Contains("example") 也会返回 true - Alexandru
5个回答

10
根据 ConfigurationProperty 类的参考源代码,这可能不是一个 bug,而是一项特性。

以下是相关的内部方法 InitDefaultValueFromTypeInfo(我进行了一些轻微的格式修改):

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);
 }
最后一个 if 块非常有趣:如果您的属性类型是 string,且默认值为 null,则默认值会被更改为 string.Empty
第一个 if 块暗示了这种特殊行为的可能解释。 [ConfigurationProperty] 属性的 DefaultValue 属性是可选的。如果程序员未设置 DefaultValue,则它本身默认为 null。第一个 if 块使用该默认的 null 来检查是否指定了 DefaultValue。如果没有,则回退到从 [DefaultValue] 属性中获取默认值(如果存在)。
所有这些意味着:指定 DefaultValue = null 的效果与根本不指定相同,在这种情况下,配置子系统会为字符串选择一个“合理”的默认值:空字符串。
解决方法:
以下是一种有些诡异的解决方法:不要将配置属性声明为 string,而是将其声明为围绕字符串的薄封装类型; 然后声明一个适当的类型转换器:
[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;
    }
}

这里是IncognitoStringIncognitoStringConverter的实现:

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;
    }
}

由于IncognitoString可以隐式转换为string,所以你可以将属性值分配给任何字符串变量。我知道,这样做很hacky并且非常复杂,只是为了获取可空属性。也许只能使用空字符串。


2
我看过的2014年至2015年左右所有问题中最好的答案! :D - Alexandru

3
另一个解决方法是进行如下调用: ```python # 示例代码 ``` ```javascript // 示例代码 ```
[ConfigurationProperty("Prompt")]
public string Prompt
{
    get { return this.GetNullableStringValue("Prompt"); }
}

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

调用GetNullableString的方式会绕过配置属性,并将DefaultValue默认为null。您也可以将该方法放在基类中,以使其更加整洁。
如果您想更改默认值,只需记住调用它即可。
如果您想获取属性定义中的其他信息,也可以调用this.ElementInformation.Properties[propertyName],但不要使用它来填充DefaultValue

谢谢,这太棒了。 - Alexandru

2

不必检查属性值是否为 null,您可以轻松地检查配置文件中是否已设置了属性或默认值是否已返回。这可以通过查看ConfigurationElementElementInformation中的ValueOrigin来完成。

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

请参阅PropertyValueOrigin 枚举的文档

1

ConfigurationElement 类型有一个 ElementInformation 属性,该属性又具有一个 IsPresent 属性。

因此,您可以检查 IsPresent 属性来查看“关联的 ConfigurationElement 对象是否在配置文件中”,而不是尝试返回空的 ConfigurationElement1

例如:

if (Configuration.Example.ElementInformation.IsPresent)
{
    ...
}

这是最佳答案,应该标记为解决方案。 - giammin

0

我选择使用更易读且可重复使用的扩展方法ToNullIfEmpty()。我保留了DefaultValue,以防将来有任何更改,以解决将null字符串转换为String.Empty的非直观行为。

[ConfigurationProperty("dataCenterRegion", DefaultValue = null)]
public string DataCenterRegion
{
    get { return ((string)this["dataCenterRegion"]).ToNullIfEmpty(); }
    set { this["dataCenterRegion"] = value; }
}

public static partial class ExtensionMethods
{        
    /// <summary>
    /// Return null if the string is empty or is already null.
    /// Otherwise, return the original string.
    /// </summary>
    public static string ToNullIfEmpty(this string str)
    {
        return String.IsNullOrEmpty(str) ? null : str;
    }

    /// <summary>
    /// Return null if the string is white space, empty or is already null.
    /// Otherwise, return the original string.
    /// </summary>
    public static string ToNullIfWhiteSpaceOrEmpty(this string str)
    {
        return String.IsNullOrWhiteSpace(str) ? null : str;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接