Задача: Можете ли вы сделать эту простую функцию более элегантной, используя C # 4.0 - PullRequest
10 голосов
/ 16 августа 2011

Когда я взломал нашу базу кода, я только что заметил эту функцию.Он преобразует IDictionary<string, object> (Paramters - переменная экземпляра) в строку 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;
}

Позвольте мне добавить еще несколько мыслей.

Мне:

  • меньше значит больше, но кратко плохо
  • больше типов в порядке, но тривиальные типы кажутся вонючими
  • многоразовость хороша

Ответы [ 5 ]

8 голосов
/ 16 августа 2011

Использование 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.

Кроме того, если бы было значение DateTime?, которое не было null, то само значение было бы упаковано, а не сама структура Nullable<DateTime>. Таким образом, фактический тип будет DateTime, поэтому этот код работает.

6 голосов
/ 24 августа 2011

Использование динамического и 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 , но уменьшает количество требуемых методов и динамических вызовов.

5 голосов
/ 19 августа 2011
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 во время выполнения будет выбрана правильная перегрузка.

Вам не нужны перегрузки для int и short, потому что они могут быть преобразованы в long (и такое преобразование считается лучше, чем преобразование в object). Но это также означает, что такие типы, как ushort и byte получат tp из int.

Кроме того, возвращаемая строка не содержит объявления XML, но в любом случае не имеет смысла объявлять, что строка в кодировке UTF-16 кодируется в кодировке UTF-8. (Если вы захотите сохранить его в файле в кодировке UTF-8 позже, лучше вернуть и сохранить XDocument и написать правильное объявление XML.)

Я думаю, что это хорошее решение, потому что оно хорошо разделяет проблемы на разные методы (можно даже перегрузить GetTypeName() и GetValue() в другой класс).

5 голосов
/ 19 августа 2011

Использование функций .net 4.0, таких как ключевые слова Tuple и dynamic.Мои тестовые примеры дают точный результат исходного вопроса.

//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
    // /3574739/prinuditelno-xdocument-dlya-zapisi-v-stroku-s-kodirovkoi-utf-8#3574744
    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)

Это ограничит динамический вызов простоэта линия.Но поскольку больше ничего не происходит, я подумал, что было бы чище, если бы не добавили дополнительный актерский состав.

2 голосов
/ 16 августа 2011

Пересмотрено с учетом новых требований.

  • Разделенные детали преобразования каждого конкретного типа и сама логика генерации 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...