c#方法能够处理/接受不同的类类型
本文关键字:类型 方法 处理 | 更新日期: 2023-09-27 18:15:20
我有一个方法,它接受一个简单的类对象并构建一个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(大概是固定的)。
为了尽可能地分离关注点,此功能通常与实体类分开实现,将它们(如果可能的话)尽可能地保留为poco,或与序列化无关的哑dto。因此,就像使用XmlSerializer
或DataContractSerializer
将类序列化为XML或Protobuf一样。NET将其序列化到协议缓冲区中,可以说最通用的方法是创建自己的序列化器。
当然,与您在日常编程中遇到的所有其他问题一样,您需要权衡潜在的好处,并决定您希望在重构中投入多少精力。如果情况很少,那么没有人会因为复制/粘贴几个硬编码方法而受到伤害,这与您现在所做的类似。同样,如果这只是一个小的"宠物项目",那么您可能不想在尝试重构成更通用的解决方案(您可能再也不需要了)时,在可能遇到的潜在问题上浪费时间。
你的目标是要写得尽可能少
然而,如果确实选择花一些时间来编写序列化器,那么您很快就会注意到,大多数序列化框架都试图尽可能地依赖约定进行序列化。换句话说,如果你的类是:public class ClientData
{
public string Name { get; set; }
public string Email { get; set; }
}
那么XmlSerializer
将在没有任何配置的情况下产生以下XML:
<ClientData>
<Name>...</Name>
<Email>...</Email>
</ClientData>
如果有一个类可以简单地为该对象输出?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;}
}
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请求的成本,您会发现它几乎可以忽略不计。
然而,如果你决定要优化,你可以很容易地在最后,在分析之后,通过改变单一的方法,通过缓存属性信息甚至编译委托来完成所有的工作。这是关注点分离的好处;重复的代码很难优化。
定义接口:
public interface IWhatsit
{
string ToApiString();
}
现在让数据对象实现它。ToApiString
应该返回这个特定对象的查询字符串部分:
public class ClientData : IWhatsit
{
public string Name {get; set;}
public string Email {get; set;}
...
public string ToApiString()
{
// Do whatever you need here - use a string builder if you want
return string.Format("Name={0}&Email={1}",Name,Email);
}
}
现在你可以有一个单一的方法来进行API调用:
public string BuildApiCall(IWhatsit thing)
{
StringBuilder sb = new StringBuilder();
sb.Append("http://mytestapi.com/");
sb.append(thing.ToApiString());
return sb.ToString();
}
注意:如果你愿意,你可以在接口中使用属性而不是方法。
另一种方法是使用抽象基类并从中继承。然后你可以这样做:
public abstract class BaseData
{
protected abstract string ToApiString();
public string BuildApiCall()
{
StringBuilder sb = new StringBuilder();
sb.Append("http://mytestapi.com/");
sb.append(ToApiString());
return sb.ToString();
}
}
然后每个类看起来像这样:
public class ClientData : BaseData
{
public string Name {get; set;}
public string Email {get; set;}
...
protected override string ToApiString()
{
// Do whatever you need here - use a string builder if you want
return string.Format("Name={0}&Email={1}",Name,Email);
}
}
允许您将BuildApiCall
放入类本身并具有基本实现。当然,如果您确实需要BuildApiCall
在这些类之外,那么您可以这样做。它只需要一个BaseData
,你必须把ToApiString
设为公共而不是保护。