如何使用属性来映射属性

时间:2017-04-27 20:45:15

标签: c# custom-attributes system.reflection

我有以下代码。

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作为集合中的Key。然后使用它来向API发布请求。所以在这种情况下,我最终会得到像......这样的集合。

collection["first_name"] = "Test"
collection["last_name"] = "Person"
collection["phone"] = "123-123-1234"
collection["email"] = "test@test.com"

我该怎么做?

4 个答案:

答案 0 :(得分:0)

假设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);

答案 1 :(得分:0)

属性属于类的属性,因此您需要获取类的类型,然后找到适当的属性,然后获取自定义属性。

类似的东西:

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) );

这会遗漏任何未标记的属性,但如果您愿意,可以决定以不同方式处理这些属性。

Fiddle

答案 2 :(得分:0)

这是

的单行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);

BUT !!!!!!!!不要试着这样做你自己。这并不像所有反射那样优化和缓慢。 这只是为了证明反射的糟糕程度

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这样的序列化器,因为你需要改变很多东西才能使它运行良好,你已经有了适合你的东西。

答案 3 :(得分:0)

如果要获取某些类型元数据,则应使用Reflection。要读取属性,您可以对MemberInfo使用GetCustomAttribute<AttributeType>()扩展名。

此扩展方法使用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);

结果与上面完全相同。