如何修改C#扩展方法GetXElementName以获取XmlElement属性名?
我定义了如下类:
public class Device { [XmlElement("MobileDeviceType")] public string DeviceType { get; set; } }
我需要实现一个名为GetXElementName的扩展方法,使其可以通过如下方式调用:
string propertyDescription = (new Device()).DeviceType.GetXElementName(); // 应返回"MobileDeviceType"
现有示例方法如下:
public static class ExtensionMethods { public static string GetXElementName<T>(this T source) { PropertyInfo prop = source.GetType().GetProperty(source.ToString()); string desc = prop.Name; object[] attrs = prop.GetCustomAttributes(true); object attr = attrs[0]; XmlElementAttribute descAttr = attr as XmlElementAttribute; if (descAttr != null) { desc = descAttr.ElementName; } return desc; } }
请问应当如何修改该方法体,以实现上述调用需求?
首先得明确:你原来的方法逻辑完全走偏了——source是属性的值(比如DeviceType对应的字符串),不是属性本身的引用。用source.ToString()去拿属性名,再从值的类型(比如string)里找这个属性,这完全不对,根本找不到你要的Device.DeviceType属性。
而且这里有个本质问题:当你调用(new Device()).DeviceType.GetXElementName()时,GetXElementName拿到的只是一个string实例,它没有任何上下文知道这个字符串是来自Device类的DeviceType属性。所以你想要的这种直接链式调用属性值的方式,没办法直接实现,因为值本身和它所属的属性没有绑定关系。
那怎么解决?我们可以换一种更合理的调用方式,用表达式树来捕获属性的信息,这样就能准确拿到目标属性上的XmlElement特性。修改后的扩展方法可以这么写:
using System; using System.Linq.Expressions; using System.Reflection; using System.Xml.Serialization; public static class ExtensionMethods { public static string GetXElementName<TObj, TProp>(this TObj obj, Expression<Func<TObj, TProp>> propertySelector) { // 解析表达式,拿到对应的PropertyInfo if (propertySelector.Body is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo prop) { throw new ArgumentException("请传入有效的属性选择表达式"); } // 获取属性上的XmlElement特性 var xmlElementAttr = prop.GetCustomAttribute<XmlElementAttribute>(); // 如果有特性就返回指定的ElementName,否则返回属性本身的名字 return xmlElementAttr?.ElementName ?? prop.Name; } }
调用方式调整为:
var device = new Device(); string propertyDescription = device.GetXElementName(d => d.DeviceType); // 返回"MobileDeviceType"
如果你真的非常想要接近你原本设想的链式调用风格,也可以给属性的类型(比如string)写扩展,但需要额外传入属性所属的对象和属性名称——但这种方式不够优雅,还容易出错:
public static string GetXElementName<TProp, TObj>(this TProp source, TObj ownerObj, string propertyName) { var prop = typeof(TObj).GetProperty(propertyName); if (prop == null) throw new ArgumentException($"类型{typeof(TObj).Name}中不存在名为{propertyName}的属性"); var xmlElementAttr = prop.GetCustomAttribute<XmlElementAttribute>(); return xmlElementAttr?.ElementName ?? prop.Name; }
调用时要这么写:
var device = new Device(); string propertyDescription = device.DeviceType.GetXElementName(device, nameof(Device.DeviceType)); // 返回"MobileDeviceType"
显然第一种用表达式树的方式更安全、更优雅,不需要手动写属性名,还能在编译期检查正确性。
总结一下:你原本的调用方式从原理上无法实现,因为属性值和它所属的属性没有关联;改用表达式树的方式,既能满足获取特性的需求,又能保证代码的健壮性。
内容的提问来源于stack exchange,提问作者Hondaputha

