我有一个方法可以接受一个简单的类对象并构建一个用于 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;}
...
}
下面是两个示例方法。如您所见,它们非常相似。将这些实现为接受不同参数的不同方法更好,还是可以编写一种方法来处理参数对象差异?
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();
}
最佳答案
决定采用哪种方法
本质上,您的问题是根据提供的 API(可能已修复)为您的类创建自定义序列化程序。
收件人separate concerns尽可能多地,此功能通常与您的实体类分开实现,尽可能将它们(如果可能的话)保留为 POCO,或独立于序列化的哑 DTO。因此,就像您使用 XmlSerializer
或 DataContractSerializer
将类序列化为 XML,或使用 Protobuf.NET 将其序列化为 Protocol Buffer 一样,可以说最通用的方法是是创建自己的序列化程序。
当然,与您在日常编程中遇到的所有其他问题一样,您需要权衡潜在 yield 并决定要在重构上投入多少精力。如果您的案例数量很少,那么没有人会因为一些复制/粘贴的硬编码方法而受到伤害,类似于您现在正在做的事情。此外,如果这只是一个很小的“宠物项目”,那么您可能会决定不想在尝试重构为更通用的解决方案(您可能再也不需要)时可能遇到的潜在问题上浪费时间。
你的目标是尽可能少地写
但是,如果您确实选择花一些时间编写序列化程序,那么您很快就会注意到大多数序列化框架都尽量依赖约定进行序列化尽可能。换句话说,如果您的类(class)是:
public class ClientData
{
public string Name { get; set; }
public string Email { get; set; }
}
然后 XmlSerializer
将在根本没有任何配置的情况下生成以下 XML:
<ClientData>
<Name>...</Name>
<Email>...</Email>
</ClientData>
拥有一个可以简单地为该对象吐出 ?name=...&email=...
的类也非常酷,而您这边绝对不需要额外的工作。如果可行,那么您将拥有一个类,它不仅可以从现有代码中删除重复项,而且可以为 API 的所有 future 扩展节省大量时间。
因此,如果您正在编写基于 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;}
}
MyApiName
属性非常简单,只接受一个字符串参数:
public class MyApiNameAttribute : Attribute
{
private readonly string _name;
public string Name
{ get { return _name; } }
public MyApiNameAttribute(string name)
{ _name = name; }
}
有了它,我们现在可以使用一些反射来呈现查询:
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();
}
用法:
var payment = new PaymentData()
{
Description = "some stuff",
PaymentAmount = 50.0m
};
// this will produce "payment=50.0&description=some%20stuff"
var query = MyApiSerializer.Serialize(payment)
性能
如评论中所述,反射的力量确实会导致性能下降。在大多数情况下,这不必太担心。在这种情况下,如果将构建查询字符串的成本(可能在 10 微秒范围内)与执行 HTTP 请求的成本进行比较,您会发现它几乎可以忽略不计。
但是,如果您决定要优化,您可以在分析结束后轻松地进行优化,方法是更改由 caching property information 完成所有工作的单一方法。甚至 compiling delegates .这对关注点分离有好处;重复的代码很难优化。
关于C# 方法能够处理/接受不同的类类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25018519/