如何使用属性映射属性
How to use attribute to map properties
我有以下代码。
public class SyncProperty : Attribute
{
public readonly string PropertyName;
public SyncProperty(string propertyName)
{
this.PropertyName = propertyName;
}
}
public class SyncContact
{
[SyncProperty("first_name")]
public string FirstName { get; set; }
[SyncProperty("last_name")]
public string LastName { get; set; }
[SyncProperty("phone")]
public string Phone { get; set; }
[SyncProperty("email")]
public string Email { get; set; }
}
我需要创建一个 SyncContact 实例,例如
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test@test.com"};
然后我需要使用该对象创建一个 NameValueCollection,其中对象的 属性 使用 SyncProperty 的 PropertyName 作为集合中的键。然后使用它向 API 发出 post 请求。所以在这种情况下,我最终会得到一个像...
这样的集合
collection["first_name"] = "Test"
collection["last_name"] = "Person"
collection["phone"] = "123-123-1234"
collection["email"] = "test@test.com"
我该怎么做?
假设每个 属性 上都标记了 SyncProperty
,这应该可以完成工作:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test@test.com"};
var collection = contact.GetType().GetProperties()
.Select(x => new
{
x.GetCustomAttribute<SyncProperty>().PropertyName,
Value = x.GetValue(contact).ToString()
})
.ToDictionary(x => x.PropertyName, x => x.Value);
作为辅助方法:
public static class SynxHelper
{
public static Dictionary<string, string> Serialize<T>(T obj)
{
return typeof(T).GetProperties()
.Select(x => new
{
SyncProperty = x.GetCustomAttribute<SyncProperty>(),
Value = x.GetValue(obj)
})
.Where(x => x.SyncProperty != null)
.ToDictionary(x => x.SyncProperty.PropertyName, x => x.Value.ToString());
}
}
// usage
var collection = SynxHelper.Serialize(contact);
属性属于class的属性,所以需要获取class的类型,然后找到合适的属性,然后获取自定义属性。
类似于:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test@test.com"};
var t = typeof(SyncContact);
var props = t.GetProperties().Select(p => new { p, attr = p.GetCustomAttribute<SyncProperty>() }).Where(p => p.attr != null);
var dict = props.ToDictionary(p => p.attr.PropertyName, v => v.p.GetValue(contact) );
这会排除所有未标记的属性,但您可以根据需要决定以不同方式处理这些属性。
这是针对此问题的单行 linq 解决方案
(from prop in obj.GetType().GetProperties()
where prop.GetCustomAttribute<SyncProperty>() != null
select new { Key = prop.GetCustomAttribute<SyncProperty>().PropertyName, Value = prop.GetValue(obj) })
.ToDictionary(k => k.Key, v => v.Value);
但是!!!!!!!!!不要尝试自己这样做。这没有像所有反射一样优化和缓慢。
这只是为了证明反射有多糟糕
static void Main(string[] args)
{
var data = Enumerable.Range(0, 10000).Select(i => new SyncContact { FirstName = "Test", LastName = "Person", Phone = "123-123-1234", Email = "test@test.com" }).ToArray();
Stopwatch sw = new Stopwatch();
long m1Time = 0;
var total1 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary(item);
total1++;
}
sw.Stop();
m1Time = sw.ElapsedMilliseconds;
sw.Reset();
long m2Time = 0;
var total2 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary2(item);
total2++;
}
sw.Stop();
m2Time = sw.ElapsedMilliseconds;
Console.WriteLine($"ToSyncDictionary : {m1Time} for {total1}");
Console.WriteLine($"ToSyncDictionary2 : {m2Time} for {total2}");
Console.ReadLine();
}
public static IDictionary<string, string> ToSyncDictionary<T>(T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new
{
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
public static IDictionary<string, string> ToSyncDictionary2<T>(T value)
{
return Mapper<T>.ToSyncDictionary(value);
}
public static class Mapper<T>
{
private static readonly Func<T, IDictionary<string, string>> map;
static Mapper()
{
map = ObjectSerializer();
}
public static IDictionary<string, string> ToSyncDictionary(T value)
{
return map(value);
}
private static Func<T, IDictionary<string, string>> ObjectSerializer()
{
var type = typeof(Dictionary<string, string>);
var param = Expression.Parameter(typeof(T));
var newExp = Expression.New(type);
var addMethod = type.GetMethod(nameof(Dictionary<string, string>.Add), new Type[] { typeof(string), typeof(string) });
var toString = typeof(T).GetMethod(nameof(object.ToString));
var setData = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select Expression.ElementInit(addMethod,
Expression.Constant(name),
Expression.Condition(Expression.Equal(Expression.Property(param, p), Expression.Constant(null)),
Expression.Call(Expression.Property(param, p), toString),
Expression.Constant(null,typeof(string))));
return Expression.Lambda<Func<T, IDictionary<string, string>>>(Expression.ListInit(newExp, setData), param).Compile();
}
}
在我的机器上,我得到了 10 倍的提升。
如果您可以使用像 JSON.net 这样的序列化程序,因为您需要更改很多东西才能使其正常工作,并且您已经有了适合您的东西。
如果你想获取一些类型元数据,你应该使用 Reflection. To read attribute you can use GetCustomAttribute<AttributeType>()
MemberInfo 扩展。
此扩展方法从用 SyncPropertyAttribute
修饰的类型属性构建同步字典(我建议使用字典而不是 NameValueCollection):
public static Dictionary<string, string> ToSyncDictionary<T>(this T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new {
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
用法:
var collection = contact.ToSyncDictionary();
输出:
{
"first_name": "Test",
"last_name": "Person",
"phone": "123-123-1234",
"email": "test@test.com"
}
注意:如果您打算在 POST 请求中使用联系人数据,那么您应该考虑使用简单的 JSON 序列化属性,而不是创建您自己的属性。例如。 Json.NET:
public class SyncContact
{
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("phone")]
public string Phone { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
}
然后简单的序列化就可以完成工作:
string json = JsonConvert.SerializeObject(contact);
结果将与上面完全相同。
我有以下代码。
public class SyncProperty : Attribute
{
public readonly string PropertyName;
public SyncProperty(string propertyName)
{
this.PropertyName = propertyName;
}
}
public class SyncContact
{
[SyncProperty("first_name")]
public string FirstName { get; set; }
[SyncProperty("last_name")]
public string LastName { get; set; }
[SyncProperty("phone")]
public string Phone { get; set; }
[SyncProperty("email")]
public string Email { get; set; }
}
我需要创建一个 SyncContact 实例,例如
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test@test.com"};
然后我需要使用该对象创建一个 NameValueCollection,其中对象的 属性 使用 SyncProperty 的 PropertyName 作为集合中的键。然后使用它向 API 发出 post 请求。所以在这种情况下,我最终会得到一个像...
这样的集合collection["first_name"] = "Test"
collection["last_name"] = "Person"
collection["phone"] = "123-123-1234"
collection["email"] = "test@test.com"
我该怎么做?
假设每个 属性 上都标记了 SyncProperty
,这应该可以完成工作:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test@test.com"};
var collection = contact.GetType().GetProperties()
.Select(x => new
{
x.GetCustomAttribute<SyncProperty>().PropertyName,
Value = x.GetValue(contact).ToString()
})
.ToDictionary(x => x.PropertyName, x => x.Value);
作为辅助方法:
public static class SynxHelper
{
public static Dictionary<string, string> Serialize<T>(T obj)
{
return typeof(T).GetProperties()
.Select(x => new
{
SyncProperty = x.GetCustomAttribute<SyncProperty>(),
Value = x.GetValue(obj)
})
.Where(x => x.SyncProperty != null)
.ToDictionary(x => x.SyncProperty.PropertyName, x => x.Value.ToString());
}
}
// usage
var collection = SynxHelper.Serialize(contact);
属性属于class的属性,所以需要获取class的类型,然后找到合适的属性,然后获取自定义属性。
类似于:
var contact = new SyncContact { FirstName="Test", LastName="Person", Phone="123-123-1234", Email="test@test.com"};
var t = typeof(SyncContact);
var props = t.GetProperties().Select(p => new { p, attr = p.GetCustomAttribute<SyncProperty>() }).Where(p => p.attr != null);
var dict = props.ToDictionary(p => p.attr.PropertyName, v => v.p.GetValue(contact) );
这会排除所有未标记的属性,但您可以根据需要决定以不同方式处理这些属性。
这是针对此问题的单行 linq 解决方案
(from prop in obj.GetType().GetProperties()
where prop.GetCustomAttribute<SyncProperty>() != null
select new { Key = prop.GetCustomAttribute<SyncProperty>().PropertyName, Value = prop.GetValue(obj) })
.ToDictionary(k => k.Key, v => v.Value);
但是!!!!!!!!!不要尝试自己这样做。这没有像所有反射一样优化和缓慢。 这只是为了证明反射有多糟糕
static void Main(string[] args)
{
var data = Enumerable.Range(0, 10000).Select(i => new SyncContact { FirstName = "Test", LastName = "Person", Phone = "123-123-1234", Email = "test@test.com" }).ToArray();
Stopwatch sw = new Stopwatch();
long m1Time = 0;
var total1 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary(item);
total1++;
}
sw.Stop();
m1Time = sw.ElapsedMilliseconds;
sw.Reset();
long m2Time = 0;
var total2 = 0;
sw.Start();
foreach (var item in data)
{
var a = ToSyncDictionary2(item);
total2++;
}
sw.Stop();
m2Time = sw.ElapsedMilliseconds;
Console.WriteLine($"ToSyncDictionary : {m1Time} for {total1}");
Console.WriteLine($"ToSyncDictionary2 : {m2Time} for {total2}");
Console.ReadLine();
}
public static IDictionary<string, string> ToSyncDictionary<T>(T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new
{
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
public static IDictionary<string, string> ToSyncDictionary2<T>(T value)
{
return Mapper<T>.ToSyncDictionary(value);
}
public static class Mapper<T>
{
private static readonly Func<T, IDictionary<string, string>> map;
static Mapper()
{
map = ObjectSerializer();
}
public static IDictionary<string, string> ToSyncDictionary(T value)
{
return map(value);
}
private static Func<T, IDictionary<string, string>> ObjectSerializer()
{
var type = typeof(Dictionary<string, string>);
var param = Expression.Parameter(typeof(T));
var newExp = Expression.New(type);
var addMethod = type.GetMethod(nameof(Dictionary<string, string>.Add), new Type[] { typeof(string), typeof(string) });
var toString = typeof(T).GetMethod(nameof(object.ToString));
var setData = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select Expression.ElementInit(addMethod,
Expression.Constant(name),
Expression.Condition(Expression.Equal(Expression.Property(param, p), Expression.Constant(null)),
Expression.Call(Expression.Property(param, p), toString),
Expression.Constant(null,typeof(string))));
return Expression.Lambda<Func<T, IDictionary<string, string>>>(Expression.ListInit(newExp, setData), param).Compile();
}
}
在我的机器上,我得到了 10 倍的提升。 如果您可以使用像 JSON.net 这样的序列化程序,因为您需要更改很多东西才能使其正常工作,并且您已经有了适合您的东西。
如果你想获取一些类型元数据,你应该使用 Reflection. To read attribute you can use GetCustomAttribute<AttributeType>()
MemberInfo 扩展。
此扩展方法从用 SyncPropertyAttribute
修饰的类型属性构建同步字典(我建议使用字典而不是 NameValueCollection):
public static Dictionary<string, string> ToSyncDictionary<T>(this T value)
{
var syncProperties = from p in typeof(T).GetProperties()
let name = p.GetCustomAttribute<SyncProperty>()?.PropertyName
where name != null
select new {
Name = name,
Value = p.GetValue(value)?.ToString()
};
return syncProperties.ToDictionary(p => p.Name, p => p.Value);
}
用法:
var collection = contact.ToSyncDictionary();
输出:
{
"first_name": "Test",
"last_name": "Person",
"phone": "123-123-1234",
"email": "test@test.com"
}
注意:如果您打算在 POST 请求中使用联系人数据,那么您应该考虑使用简单的 JSON 序列化属性,而不是创建您自己的属性。例如。 Json.NET:
public class SyncContact
{
[JsonProperty("first_name")]
public string FirstName { get; set; }
[JsonProperty("last_name")]
public string LastName { get; set; }
[JsonProperty("phone")]
public string Phone { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
}
然后简单的序列化就可以完成工作:
string json = JsonConvert.SerializeObject(contact);
结果将与上面完全相同。