将字符串转换为 <T> 类型

83

我真的应该能够理解这个问题,但现在已经到了更容易询问的地步。

在C#函数中:

public static T GetValue<T>(String value) where T:new()
{
   //Magic happens here
}

有什么好的方法来实现这个“magic”(神奇)吗?这个想法的背后是我需要解析xml,并且所需的值通常是原始类型(bool、int、string等),这是使用泛型的完美场所...但是一个简单的解决方案暂时困扰着我。

顺便说一下,这是我需要解析的xml样例:

<Items>
    <item>
        <ItemType>PIANO</ItemType>
        <Name>A Yamaha piano</Name>
        <properties>
            <allowUpdates>false</allowUpdates>
            <allowCopy>true</allowCopy>
        </properties>   
    </item>
    <item>
        <ItemType>PIANO_BENCH</ItemType>
        <Name>A black piano bench</Name>
        <properties>
            <allowUpdates>true</allowUpdates>
            <allowCopy>false</allowCopy>
            <url>www.yamaha.com</url>
        </properties>
    </item>
    <item>
        <ItemType>DESK_LAMP</ItemType>
        <Name>A Verilux desk lamp</Name>
        <properties>
            <allowUpdates>true</allowUpdates>
            <allowCopy>true</allowCopy>
            <quantity>2</quantity>
        </properties>
    </item>
</Items>

你能提供一下你尝试解析的XML样本吗? - bendewey
想提供一下你期望得到的样本吗?在这种情况下,T 可以是很多东西... 你能否将字符串转换为它们将取决于你定义允许内容的能力。 - Shog9
5个回答

225
我建议你不要尝试自己解析XML,而是尝试创建类,将XML反序列化到这些类中。我强烈建议遵循bendewey的答案。
但是,如果你无法做到这一点,也有希望。你可以使用Convert.ChangeType
public static T GetValue<T>(String value)
{
  return (T)Convert.ChangeType(value, typeof(T));
}

然后像这样使用

GetValue<int>("12"); // = 12
GetValue<DateTime>("12/12/98");

我会跟上你的+1并再加上一个+1。虽然我对它的优雅感到困惑,但这似乎是为了使用泛型而使用泛型。 - John Farrell
让我们就 GetValue<int>() 和 (int)GetValue() 的美学进行争论 :P - Jimmy
序列化对于此函数的预期使用来说不可行。我们最初考虑了Xml <-> poco,但当xml达到> 1Mb范围时,它会使应用程序变得缓慢。因此,xpath是正确的选择。 - Saint Domino
1
这个方案可行,但不适用于可空类型。请查看这个StackOverflow问题获取解决方案。 - Marcel Posdijk

11
您可以从以下大致内容开始:

您可以从以下大致内容开始:

TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
   return (T)converter.ConvertFrom(value);
}

如果您需要解析特殊类型的属性,比如颜色或文化字符串等,当然需要在上述内容中构建特殊情况。但是这将处理大部分基本类型。

如果您使用GetConverter(typeof(T)),则不需要t变量或new()。 - Marc Gravell
是的,我很快地草拟了它。也许我会进行编辑 - 谢谢。 - womp

4
如果您决定采用序列化到POCO(普通旧CLR对象)的路线,则有一些工具可以帮助您生成对象。
  • 您可以使用xsd.exe基于XML定义生成.cs文件
  • WCF REST Starter Kit Preview 2中有一个新功能,称为Paste as Html。此功能非常酷,它允许您将剪贴板中的HTML块粘贴到cs文件中时自动将xml转换为CLR对象进行序列化。

序列化对于此函数的预期使用来说不可行。我们最初考虑了Xml <-> poco,但当xml达到> 1Mb范围时,它会使应用程序变得缓慢。老板说xpath是正确的选择。 - Saint Domino
LINQ to XML比xpath更快。 - bendewey
请参阅http://en.wikipedia.org/wiki/XML#Processing_files,XPath使用DOM,Linq to XML使用'pull parsing'。 - bendewey
2
记住,我们中的一些人将在 .NET 2.0 的世界里困扰一段时间... :-) - Saint Domino
哎呀,听到这个消息真的很抱歉。 - bendewey

0
为了使其正常工作,您的通用方法将不得不将其实际工作委托给专用类。
例如:
private Dictionary<System.Type, IDeserializer> _Deserializers;
    public static T GetValue<T>(String value) where T:new()
    {
       return _Deserializers[typeof(T)].GetValue(value) as T;
    }

_Deserializers是某种字典,您可以在其中注册您的类。(显然,需要进行一些检查以确保在字典中已经注册了反序列化程序)。

(在这种情况下,where T:new()是无用的,因为您的方法不需要创建任何对象。)


在没有 where 类约束的情况下,您不能在泛型类型上使用 as 运算符。 - Samuel
哎呀,我不知道那个。你仍然可以进行常规转换吧? - Denis Troller

0

有一个警告需要再次强调,这样做可能不是一个好主意:

class Item 
{
    public string ItemType { get; set; }
    public string Name { get; set; }
}

public static T GetValue<T>(string xml) where T : new()
{
    var omgwtf = Activator.CreateInstance<T>();
    var xmlElement = XElement.Parse(xml);
    foreach (var child in xmlElement.Descendants())
    {
        var property = omgwtf.GetType().GetProperty(child.Name.LocalName);
        if (property != null) 
            property.SetValue(omgwtf, child.Value, null);
    }
    return omgwtf;
}

测试运行:

static void Main(string[] args)
{
    Item piano = GetValue<Item>(@"
        <Item>
            <ItemType />
            <Name>A Yamaha Piano</Name>
            <Moose>asdf</Moose>
        </Item>");
}

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