【问题标题】:C# converting string data representation to typedC# 将字符串数据表示形式转换为类型化
【发布时间】:2011-04-22 08:26:30
【问题描述】:

现在我正在编写一个从 XML 加载配置数据的通用方法。我的案例中很多参数都存储在节点属性中,所以我决定写一个通用的属性读取方法:

    private static T ReadAttribute<T>(XElement Element,string AttributeName)
    {
        var attrib = Element.Attribute(AttributeName);

        if (attrib != null)
        {
            return attrib.Value; // off cource error is in this line !
        }
        else
        {
            return default(T);
        }
    }

此方法应尝试读取具有指定名称的属性,如果此属性丢失,则应返回属性类型的默认值。属性类型由 T 指定。 正如上面的评论所示,我的问题是我无法将字符串值普遍转换为特定类型。实际上我计划使用 int、double 和两种枚举类型作为 T。
在这种情况下我应该采取什么行动?我应该如何将字符串值转换为 T 类型?
提前致谢!

【问题讨论】:

    标签: c# type-conversion


    【解决方案1】:

    您可以使用Convert.ChangeType。它基本上可以满足您的需求。但这是转换而不是演员表,嗯,不仅仅是演员表。

    return (T)Convert.ChangeType(attrib.Value, typeof(T), CultureInfo.InvariantCulture);
    

    您可以简单地将字符串转换为任意类型的原因是类型系统不允许这样做。但是Convert.ChangeType 返回的对象可以是任何类型,因此允许强制转换。

    CultureInfo.InvariantCulture 很重要,因为不应使用不同的文化对 XML 内容进行编码/解码。如果使用 XML,则应使用 XmlConvert 类,但它没有像 XmlConvert.ChangeType 这样方便的通用方法。

    XAttribute 类具有许多映射到 XmlConvert 类的显式用户定义转换。但是,您不能简单地将它们与不受约束的类型参数 T 一起使用并期望得到相同的结果。

    更糟糕的是,XML 和 Convert 实际上并不能很好地发挥作用。所以如果你真的很认真,你会写这样的东西来处理转换。

    static T ConvertTo<T>(XAttribute attr)
    {
        object value;
        switch (Type.GetTypeCode(typeof(T)))
        {
            case TypeCode.Boolean: value = XmlConvert.ToBoolean(attr.Value); break;
            case TypeCode.Int32: value = XmlConvert.ToInt32(attr.Value); break;
            case TypeCode.DateTime: value = XmlConvert.ToDateTime(attr.Value); break;
            // Add support for additional TypeCode values here... 
            default:
                throw new ArgumentException(string.Format("Unsupported destination type '{0}'.", typeof(T)));
        }
        return (T)value;
    }
    

    【讨论】:

    • 对于那些想知道的人,Convert.ChangeType 需要源对象来实现 IConvertible(除非源已经是所需的类型)。
    • XAttribute 已经完成了正向转换(使用 XmlConvert 中的方法),但反向转换将通过 Convert 完成,这是否存在危险?我不知道是否有极端情况,例如 DateTime,转换可能会有所不同?
    • 是的!它现在完美运行。谢谢!但是我怎么能确定,例如,我不能将我的方法与假设的 classA 一起使用,因为 dint 存在从字符串的转换。据我了解,简单地指定where T: IConvertible 是不对的,它不会产生足够的结果
    • @Will Dean - 是的。在大多数情况下,使用Convert.ChangeType 会起作用,但是XmlConvert 类正在完成一些Convert 类没有完成的特定事情。最好推出自己的ConvertTo&lt;T&gt;,它可以与 XML 内容一起正常工作。
    • @Anton Semenov 你真的不能,如果不可能的话,它会抛出一个运行时异常。这样做的好处是它的语法非常简洁,但它确实有缺陷。
    【解决方案2】:

    我会选择TypeConverter 的东西。它基本上是一个转换价值观和文化的课程。 TypeConverter 和Convert.ChangeType 的主要区别在于后者需要源类型上的 IConvertible 接口,而 TypeConverters 可以处理任何对象。

    我为此创建了一个辅助类,因为我经常将不同的配置对象存储在 xml 文件中。这也是为什么在 CultureInfo.InvariantCulture 之间进行硬编码转换的原因。

    public static class TypeConversion {
        public static Object Convert(Object source, Type targetType) {
            var sourceType = source.GetType();
            if (targetType.IsAssignableFrom(sourceType))
                return source;
    
            var sourceConverter = TypeDescriptor.GetConverter(source);
            if (sourceConverter.CanConvertTo(targetType))
                return sourceConverter.ConvertTo(null, CultureInfo.InvariantCulture, source, targetType);
    
            var targetConverter = TypeDescriptor.GetConverter(targetType);
            if (targetConverter.CanConvertFrom(sourceType))
                return targetConverter.ConvertFrom(null, CultureInfo.InvariantCulture, source);
    
            throw new ArgumentException("Neither the source nor the target has a TypeConverter that supports the requested conversion.");
        }
    
    
        public static TTarget Convert<TTarget>(object source) {
            return (TTarget)Convert(source, typeof(TTarget));
        }
    }
    

    完全可以创建自己的 TypeConverter 来处理系统类型,例如 System.Version(它不实现 IConvertible),以支持从包含版本号(“a.b.c.d”)的字符串到实际版本对象的转换。

    public class VersionTypeConverter : TypeConverter {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
            if (sourceType == typeof(string))
                return true;
    
            return base.CanConvertFrom(context, sourceType);
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
            var s = value as string;
            if (s != null)
                return new Version(s);
    
            return base.ConvertFrom(context, culture, value);
        }
    
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
            if (destinationType == typeof(string))
                return true;
    
            return base.CanConvertTo(context, destinationType);
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
            var v = value as Version;
            if (v != null && destinationType == typeof(string)) {
                return v.ToString();
            }
    
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    

    要实际使用此提供程序,您需要在应用程序启动期间注册它,使用TypeDescriptor.AddProvider,传入自定义TypeDescriptionProvidertypeof(Version)。这需要在TypeDescriptorProvider.GetTypeDescriptor方法中返回一个自定义的CustomTypeDescriptor,并且描述符需要重写GetConverter返回一个新的VersionTypeConverter实例。简单的。 ;)

    【讨论】:

    • 哇!它真的很强大的解决方案。现在我试图在我的程序中使用它。谢谢!
    • 我相信所有实现 IConvertible 的系统类型也已经有了一个 TypeConverter。需要这么多代码的主要原因是 TypeDescriptor 的东西包装了整个反射框架。覆盖 CustomTypeDescriptor.GetProperties 以添加/删除属性,使用 TypeDescriptor.GetProperties 的人将看到新内容。如果需要,您可以自行构建自己的属性并自行处理它们的存储。
    • @SimonSvensson 非常方便,而且是蝙蝠侠的老贴士,但是在尝试 ConvertFrom 之前尝试 ConvertTo 是否有原因,如果是这样,您能否将其添加到您的 (现在 3 岁) 回答?对于我的用例,我相信我会比 ConvertTo 更频繁地需要 ConvertFrom,因为我正在使用自定义 ORM,它将数据库中的整数、字符串等映射到已知的自定义类型;一个 int 转换器。CanConvertTo() 将始终返回 false,并且读取发生的频率比写入要频繁得多,所以我将翻转该顺序,除非我错过了某些东西 (除了不要编写/使用自定义 ORM )
    • @Darmon,从来没想过。除非源转换器的 ConvertTo 和目标转换器的 ConvertFrom 都声称它们可以转换为另一种类型,并且它们在实现上有所不同,否则应该没有任何问题。有点人为的例子,我想不出任何内置的东西可以做到这一点。
    • @SimonSvensson 是的,这似乎不仅很少见,而且可能是实施中的错误;至少我能想到的任何奇怪的案例! :-D 我不确定你是不是因为性能问题而按照这个顺序实现的,或者可能是在ConvertFrom() 之前调用ConvertTo() 的约定。感谢您的回复,我将首先使用ConvertFrom(),因为我会更频繁地使用它,这样会减少几个第一个 ifs。这不应该有很大的不同,但应该少一点浪费。
    【解决方案3】:

    如果 T 是您自己定义的类型,内置方法将无济于事。假设xml看起来像:

    //some other segments
    <Book Name="Good book" Price="20" Author="Jack" />
    

    而你 T 是类 Book 的样子:

    class Book
    {
       public string Name { get; set; }
       public decimal Price { get; set; }
       public string Author { get; set; }
         //maybe some other properties
    }
    

    自动将XElement 转换为Book 的实例没有什么神奇的,你需要自己实现。一个简单而通用的实现是这样的:

        interface IXElementConvertible
        {
            void LoadFrom(XElement element);
        }
    
        class Book : IXElementConvertible
        {
            public string Name { get; set; }
            public decimal Price { get; set; }
            public string Author { get; set; }
    
            public void LoadFrom(XElement element)
            {
                this.Name = element.Attribute("Name").Value;
                //blabla
            }
        }
    

    你需要修改你的方法:

    private static T ReadAttribute<T>(XElement Element,string AttributeName) 
                                 where T : IXElementConvertible, new()
    {
        T t = new T();
        t.LoadFrom(element);
           //just an example here, not the complete implementation
    }
    

    【讨论】:

      【解决方案4】:

      我认为您应该在代码中进行以下检查:

      if (attrib.Value is T)
      {
      ...
      }
      

      【讨论】:

      • 就像@Dummy01 一样工作。如果您错了,您将投反对票,因为我们不想宣传错误的答案。但是,如果您是真诚的并且不想了解更多信息,我很乐意为您提供帮助。您的建议实际上完全无视类型系统的规则,并且没有任何意义。 Value 属性是 System.String 类型,如果 T 不是字符串,它永远不会对 T 进行类型测试。我看不出这样做的意义,它不能解决问题,而且我很清楚,你没有理解这个问题。
      • 你花了太多的台词让老师给我。我想它非常适合您的直接否定投票。但是对于这个故事,是的,你是对的。我错误地记得 Value 是一个对象,而不是一个字符串,这就是我的支票的来源。事实上,如果你注意到我刚才说的我做错了,这是解决你问题的最简单方法,而且你也会使用我的支票。但我不会冒老师再次投错票的风险,所以我想你必须自己弄清楚:)
      猜你喜欢
      • 1970-01-01
      • 2021-02-20
      • 2022-01-23
      • 1970-01-01
      • 2015-08-21
      • 1970-01-01
      • 2012-01-06
      • 2018-01-23
      • 1970-01-01
      相关资源
      最近更新 更多