C#方法能够处理/接受不同的类类型

web*_*orm 4 c#

我有一个方法接受一个简单的类对象,并构建一个API调用中使用的URL.我希望这个方法能够处理/接受相似但具有不同属性的不同类类型.

public class ClientData
{
   public string Name {get; set;}
   public string Email {get; set;}
   ...
}

public class PaymentData
{
   public decimal PaymentAmount {get; set;}
   public string Description {get; set;}
   ...
}
Run Code Online (Sandbox Code Playgroud)

以下是两个示例方法.如你所见,它们非常相似.最好是将这些实现为接受不同参数的不同方法,还是可以编写一个可以处理参数对象差异的方法?

public string BuildApiCall(ClientData clientDataObject)
{
  StringBuilder sb = new StringBuilder();
  sb.Append("http://mytestapi.com/");
  sb.append("name=" + clientDataObject.Name);
  sb.append("email=" + clientDataObject.Email);
  return sb.ToString();
}

public string BuildApiCall(PaymentData paymentDataObject)
{
  StringBuilder sb = new StringBuilder();
  sb.Append("http://mytestapi.com/");
  sb.append("payment=" + paymentDataObject.PaymentAmount );
  sb.append("description=" + paymentDataObject.Description );
  return sb.ToString();
}
Run Code Online (Sandbox Code Playgroud)

Gro*_*roo 7

决定采取哪种方法

实质上,您的问题是根据提供的API(可能是固定的)为您的类创建自定义序列化程序.

为了尽可能地分离关注点,此功能通常与您的实体类分开实现,尽可能将它们(如果可能的话)保留为POCO或与序列化无关的哑DTO.因此,就像您将一个XmlSerializer或一个DataContractSerializer类序列化为XML或Protobuf.NET将其序列化为协议缓冲区一样,可以说最常用的方法是创建自己的序列化程序.

当然,正如您在日常编程中遇到的所有其他问题一样,您需要权衡潜在的好处并决定您希望投入多少精力进行重构.如果您的案例数量很少,那么没有人会受到一些复制/粘贴硬编码方法的伤害,类似于您现在正在做的事情.此外,如果这只是一个小小的"宠物项目",那么您可能会认为您不想浪费时间在尝试重构为更通用的解决方案(您可能永远不会再需要)时可能遇到的潜在问题.

你的目标是尽可能少地写作

但是,如果您确实选择花一些时间编写序列化程序,那么您将很快注意到大多数序列化框架尽可能地依赖于序列化的约定.换句话说,如果你的课程是:

public class ClientData
{
    public string Name { get; set; }
    public string Email { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后XmlSerializer,没有任何配置的遗嘱会产生以下XML:

<ClientData>
    <Name>...</Name>
    <Email>...</Email>
</ClientData>
Run Code Online (Sandbox Code Playgroud)

拥有一个简单地?name=...&email=...为该对象吐出的课程也是非常酷的,绝对没有额外的工作.如果可行,那么您所拥有的课程不仅可以删除现有代码中的重复内容,还可以为将来的API扩展节省时间.

因此,如果您正在基于API编写类,那么尽可能将属性命名为API成员(并使用基于约定的序列化)可能是有意义的,但仍然保持开放足以能够处理几个边缘情况分开.

示例代码

public class ClientData
{
    public string Name {get; set;}
    public string Email {get; set;}
}

// customer really insisted that the property is
// named `PaymentAmount` as opposed to simply `Amount`,
// so we'll add a custom attribute here
public class PaymentData
{
    [MyApiName("payment")]
    public decimal PaymentAmount {get; set;}
    public string Description {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

MyApiName属性非常简单,只接受一个字符串参数:

public class MyApiNameAttribute : Attribute
{
    private readonly string _name;
    public string Name
    { get { return _name; } }

    public MyApiNameAttribute(string name)
    { _name = name; }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我们现在可以使用一些反射来呈现查询:

public static string Serialize(object obj)
{
    var sb = new StringBuilder();

    foreach (var p in obj.GetType().GetProperties())
    {
        // default key name is the lowercase property name
        var key = p.Name.ToLowerInvariant();

        // we need to UrlEncode all values passed to an url
        var value = Uri.EscapeDataString(p.GetValue(obj, null).ToString());

        // if custom attribute is specified, use that value instead
        var attr = p
            .GetCustomAttributes(typeof(MyApiNameAttribute), false)
            .FirstOrDefault() as MyApiNameAttribute;

        if (attr != null)
            key = attr.Name;

        sb.AppendFormat(
            System.Globalization.CultureInfo.InvariantCulture,
            "{0}={1}&",
            key, value);
    }

    // trim trailing ampersand
    if (sb.Length > 0 && sb[sb.Length - 1] == '&')
        sb.Length--;

    return sb.ToString();
}
Run Code Online (Sandbox Code Playgroud)

用法:

var payment = new PaymentData()
{
    Description = "some stuff",
    PaymentAmount = 50.0m 
};

// this will produce "payment=50.0&description=some%20stuff"            
var query = MyApiSerializer.Serialize(payment)
Run Code Online (Sandbox Code Playgroud)

性能

正如评论中所指出的,反射的力量确实会导致性能下降.在大多数情况下,这应该不是很重要.在这种情况下,如果您将构建查询字符串的成本(可能在10微秒的范围内)与执行HTTP请求的成本进行比较,您会发现它几乎可以忽略不计.

但是,如果您决定要进行优化,则可以在分析之后,通过更改通过缓存属性信息甚至编译代理来完成所有工作的单个方法,轻松地完成此操作.分离关注点很好; 重复的代码很难优化.