【问题标题】:Challenge: Can you make this simple function more elegant using C# 4.0挑战:你能用 C# 4.0 让这个简单的函数更优雅吗
【发布时间】:2011-10-27 15:02:02
【问题描述】:

当我破解我们的代码库时,我注意到了这个函数。它将IDictionary<string, object>(参数 - 实例变量)转换为 XML 字符串。

这只是我的好奇心 :-) 。

那么它可以使用 C# 4.0 用更少的代码编写吗?规则:除了 .Net Framework BCL 之外没有外部库。

为了使它更具挑战性,我没有将输入字典规范放在这里,因为您应该能够从代码中解决它。

public string ConvertToXml() {
    XmlDocument doc = new XmlDocument();
    doc.LoadXml("<?xml version='1.0' encoding='utf-8'?><sc/>");
    foreach (KeyValuePair<string, object> param in Parameters) {
        XmlElement elm = doc.CreateElement("pr");

        if (param.Value is int || param.Value is Int32 || param.Value is Int16 || param.Value is Int64) {
            elm.SetAttribute("tp", "int");
        } else if (param.Value is DateTime?){
            elm.SetAttribute("tp", "datetime");
        } else {
            elm.SetAttribute("tp", "string");
        }

        elm.SetAttribute("nm", param.Key);
        if (param.Value is DateTime?) {
            DateTime? dateTime = param.Value as DateTime?;
            elm.SetAttribute("vl", dateTime.Value.ToString("o"));
        } else{
            elm.SetAttribute("vl", param.Value.ToString());
        }
        doc.FirstChild.NextSibling.AppendChild(elm);
    }
    return doc.OuterXml;
}

让我补充一些想法。

对我来说:

  • 少即是多,但简洁是不好的
  • 更多的类型还好,但琐碎的类型看起来很臭
  • 复用性好

【问题讨论】:

  • 如果这是一个挑战,你应该提供一些赏金:)
  • 我愿意。我不知道如何在开始时添加赏金 - 我以为你必须等待。我该怎么办?
  • 嗯,你是对的,你需要等待 2 天。 :0 我的坏。期待看到人们提出的解决方案^^
  • 只是关于代码的注释:intInt32 完全是一回事。
  • 我没想到你真的会悬赏它。但你做到了。但它是一个相当骨瘦如柴的,虽然你不觉得吗? :D

标签: xml c#-4.0


【解决方案1】:

使用 LINQ to XML 可以使编写起来非常简单。如果您可以选择,请优先使用它而不是标准 XML 库。

我相信这应该是等价的:

public static string ToXmlString(this IDictionary<string, object> dict)
{
    var doc = new XDocument(new XElement("sc", dict.Select(ToXElement)));

    using (var writer = new Utf8StringWriter())
    {
        doc.Save(writer); // "hack" to force include the declaration
        return writer.ToString();
    }
}

class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

static XElement ToXElement(KeyValuePair<string, object> kvp)
{
    var value = kvp.Value ?? String.Empty;

    string typeString;
    string valueString;
    switch (Type.GetTypeCode(value.GetType()))
    {
    case TypeCode.Int16:
    case TypeCode.Int32:
    case TypeCode.Int64:
        typeString = "int";
        valueString = value.ToString();
        break;
    case TypeCode.DateTime:
        typeString = "datetime";
        valueString = ((DateTime)value).ToString("o");
        break;
    default:
        typeString = "string";
        valueString = value.ToString();
        break;
    }

    return new XElement("pr",
        new XAttribute("tp", typeString),
        new XAttribute("nm", kvp.Key),
        new XAttribute("vl", valueString));
}

请注意,检查值是否为DateTime? 类型是没有意义的。我不确定在字典中存储 null 值有什么价值,但如果可能的话,由于生成 object 类型的值,您无论如何都会丢失该类型信息。

另外,如果有一个不是nullDateTime? 值,那么该值本身将被装箱,而不是Nullable&lt;DateTime&gt; 结构本身。所以实际的类型是DateTime,这就是这段代码有效的原因。

【讨论】:

    【解决方案2】:

    使用动态和 LINQ to XML:

    ConvertToXml 可以简化为一条语句(假设省略 XML 声明是可以接受的)。

    public string ConvertToXml()
    {
        return new XElement("sc",
            Parameters.Select(param => CreateElement(param.Key, (dynamic)param.Value))
        ).ToString(SaveOptions.DisableFormatting);
    }
    

    请注意,CreateElement 将 param.Value 转换为动态,以便在运行时从以下内容中选择正确的重载。

    XElement CreateElement(string key, object value)
    {
        return CreateElement("string", key, value.ToString());
    }
    
    XElement CreateElement(string key, long value)
    {
        return CreateElement("int", key, value.ToString());
    }
    
    XElement CreateElement(string key, DateTime value)
    {
        return CreateElement("datetime", key, value.ToString("o"));
    }
    

    上面的重载最终调用:

    XElement CreateElement(string typename, string key, string value)
    {
        return new XElement("pr",
            new XAttribute("tp", typename),
            new XAttribute("nm", key),
            new XAttribute("vl", value)
        );
    }
    

    此代码减少了问题中的语句数(尽管不是行数)。这种方法基于svick's,但减少了所需的方法和动态调用的数量。

    【讨论】:

    • 在使用dynamic的解决方案中,我最喜欢这种形式。
    • 谢谢。我最喜欢这个了!
    【解决方案3】:

    使用 .net 4.0 功能,例如 Tupledynamic 关键字。我的测试用例会产生原始问题的精确输出。

    //using System;
    //using System.Collections.Generic;
    //using System.Linq;
    //using System.Xml.Linq;
    
        public string ConvertToXml()
        {
            //Create XDocument to specification with linq-to-xml
            var doc = new XDocument(
                new XElement("sc",
                        from param in Parameters
                        //Uses dynamic invocation to use overload resolution at runtime
                        let attributeVals = AttributeValues((dynamic)param.Value)
                        select new XElement("pr",
                                    new XAttribute("tp", attributeVals.Item1),
                                    new XAttribute("nm", param.Key),
                                    new XAttribute("vl", attributeVals.Item2)
                               )
                )
            );
            //Write to string
            using (var writer = new Utf8StringWriter())
            {
                doc.Save(writer, SaveOptions.DisableFormatting);//Don't add whitespace
                return writer.ToString();
            }
        }
        //C# overloading will choose `long` as the best pick for `short` and `int` types too
        static Tuple<string, string> AttributeValues(long value)
        {
            return Tuple.Create("int", value.ToString());
        }
        //Overload for DateTime
        static Tuple<string, string> AttributeValues(DateTime value)
        {
            return Tuple.Create("datetime", value.ToString("o"));
        }
        //Overload catch all
        static Tuple<string, string> AttributeValues(object value)
        {
            return Tuple.Create("string", value.ToString());
        }
        // Using John Skeet's Utf8StringWriter trick
        // http://stackoverflow.com/questions/3871738/force-xdocument-to-write-to-string-with-utf-8-encoding/3871822#3871822
        class Utf8StringWriter : System.IO.StringWriter
        {
            public override System.Text.Encoding Encoding { get { return System.Text.Encoding.UTF8; } }
        }
    

    可选: 将 let 语句更改为:

    let attributeVals = (Tuple<string,string>)AttributeValues((dynamic)param.Value)
    

    这会将动态调用限制在该行。但由于没有太多其他事情发生,我认为不添加额外演员会更干净。

    【讨论】:

    • dynamic 的用法很有趣。我一直忘记这是我们的选择之一。
    • 大多数动态语言不提供方法重载,因此即使您习惯了它,您也倾向于认为双重调度涉及更多。这是 C# 现在成为真正的混合静态/动态语言的 ntat 副作用之一。
    【解决方案4】:
    public string ConvertToXml()
    {
        var doc = new XDocument(
            new XElement("sd",
                Parameters.Select(param =>
                    new XElement("pr",
                        new XAttribute("tp", GetTypeName((dynamic)param.Value)),
                        new XAttribute("nm", param.Key),
                        new XAttribute("vl", GetValue((dynamic)param.Value))
                        )
                    )
                )
            );
        return doc.ToString();
    }
    

    此代码假定您已重载方法 GetTypeName()GetValue() 实现为:

    static string GetTypeName(long value)
    {
        return "int";
    }
    
    static string GetTypeName(DateTime? value)
    {
        return "datetime";
    }
    
    static string GetTypeName(object value)
    {
        return "string";
    }
    
    static string GetValue(DateTime? value)
    {
        return value.Value.ToString("o");
    }
    
    static string GetValue(object value)
    {
        return value.ToString();
    }
    

    这使用了这样一个事实,即当使用dynamic 时,将在运行时选择正确的重载。

    intshort 不需要重载,因为它们可以转换为long(这种转换被认为比转换为object 更好)。但这也意味着ushortbyte 等类型将获得tpint

    此外,返回的字符串不包含 XML 声明,但无论如何声明 UTF-16 编码的字符串是 UTF-8 编码的也没有意义。 (如果您以后想将其保存在 UTF-8 编码文件中,返回并保存 XDocument 会更好,并且会编写正确的 XML 声明。)

    我认为这是一个很好的解决方案,因为它很好地将关注点分离到不同的方法中(你甚至可以将 GetTypeName()GetValue() 重载到不同的类中)。

    【讨论】:

    • 关注点真的是分开的吗?当然,在指定的情况下,intstring 的情况共享相同的 GetValue 代码,但是当我们谈论数据的分类和序列化时,如果它们是分开的,那么反过来会是真的吗(即某种类型将共享相同的 GetTypeName 方法但具有不同的 GetValue)?不...从不。
    • 我能找到差异的唯一原因是不同类型与此对象最终用于的 SQL 类型有关。但是,是的,公平点。
    • @Preet,我的意思是 svick 提到了关于 GetTypeName 和 GetValue 的关注点分离,我的意思是它们不是单独的关注点,您应该将 GetTypeNameAndValue 作为一种方法。在不同的 GetTypeName 实现之间共享 GetValue 实现实际上只是一种侥幸,而且您永远不会在不同的 GetValue 实现之间共享 GetTypeName 实现,因此将它们分成两种方法确实是一种气味,如果更多类型被添加到等式中很明显。这是 svick 从我的回答中得到的唯一实质性区别。
    • @jbtule,我的主要意思是将ConvertToXml() 和其他方法分开。我认为你的方法和我的方法没有太大区别。 (另外,我在发帖之前没有阅读您的答案,如果您更快,我什至可能不会回答。)
    • @svick 更快?那是 20 分钟——不是 20 秒。无论如何,除了省略 xml 标头和拆分名称和值之外,唯一的其他区别是您使用 DateTime? 而不是 DateTime,正如 @Jeff-Mercado 在他的回答中明确表示的那样,这是一种冗余。 Nullables 只能静态使用,一旦它被转换为object(或dynamic),clr 会将其剥离为仅值,并且您会丢失可为空的类型信息。在参考实现和您的实现中,DateTime 正在转换为 DateTime?,只是为了再次提取原始的 DateTime 值。
    【解决方案5】:

    考虑到新的要求,重新接触。

    • 解耦了每个具体类型和 XML 生成逻辑本身的转换细节
    • 可以通过向提供程序添加新工厂轻松引入新的数据类型支持。当前支持的类型集受TypeCode 枚举成员的限制,但显然这可以轻松切换到其他类型选择器/标识符。
    • 我必须同意 jbtule 的观点,即 Tuple.Create() 看起来确实比 KeyValuePair<,> 的构造要好得多,以前从未使用过,好东西,谢谢!

    方法本身:

    public string ConvertToXml(
        IDictionary<string, object> rawData, 
            Dictionary<TypeCode, Func<object, Tuple<string, string>>> transformationFactoryProvider) 
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml("<?xml version='1.0' encoding='utf-8'?><sc/>");
    
        if (rawData != null)
        {
            Func<object, Tuple<string, string>> defaultFactory = 
                  (raw) => Tuple.Create("string", raw.ToString());
    
            foreach (KeyValuePair<string, object> item in rawData)
            {
                TypeCode parameterTypeCode = Type.GetTypeCode(item.Value.GetType());
                var transformationFactory = transformationFactoryProvider.ContainsKey(parameterTypeCode)
                                                ? transformationFactoryProvider[parameterTypeCode]
                                                : defaultFactory;
    
                var transformedItem = transformationFactory(item.Value);
                XmlElement xmlElement = doc.CreateElement("pr");
                xmlElement.SetAttribute("tp", transformedItem.Item1);
                xmlElement.SetAttribute("nm", item.Key);
                xmlElement.SetAttribute("vl", transformedItem.Item2);
                doc.FirstChild.NextSibling.AppendChild(xmlElement);
            }
        }
    
        return doc.OuterXml; 
    }
    

    使用方法示例:

    // Transformation Factories
    // Input: raw object
    // Output: Item1: type name, Item2: value in the finally formatted string
    Func<object, Tuple<string, string>> numericFactory = raw => Tuple.Create("int", raw.ToString());
    Func<object, Tuple<string, string>> dateTimeFactory =
        raw => Tuple.Create("datetime", (raw as DateTime?).GetValueOrDefault().ToString("o"));
    
    // Transformation Factory Provider
    // Input: TypeCode
    // Output: transformation factory for the given type
    var transformationFactoryProvider =
        new Dictionary<TypeCode, Func<object, Tuple<string, string>>>
            {                        
                {TypeCode.Int16, numericFactory},
                {TypeCode.Int32, numericFactory},
                {TypeCode.Int64, numericFactory},
                {TypeCode.DateTime, dateTimeFactory}
            };
    
    // Convert to XML given parameters
    IDictionary<string, object> parameters = new Dictionary<string, object>
                             {
                                    { "SOMEDATA", 12 },
                                    { "INTXX", 23 },
                                    { "DTTM", DateTime.Now },
                                    { "PLAINTEXT", "Plain Text" },
                                    { "RAWOBJECT", new object() },
                              };
    string xmlParameters = this.ConvertToXml(parameters, transformationFactoryProvider);
    

    【讨论】:

    • 哦,来吧,你的 KeyValuePairs 看起来像元组更好。 Tuple.Create vs new KeyValuePair&lt;string,string&gt; 只是说。
    猜你喜欢
    • 2017-07-31
    • 2020-07-05
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多