Инициализируйте объект типа T из Словаря <строка, объект>

Я ищу более универсальный / "стандартный" способ инстанцировать объекта некоторого типа T от набора строки пар, объекта. Для меня похоже, что должен быть некоторый известный способ сделать это, но я не могу найти его, таким образом, я придумываю эту часть кода. Кто-либо знает что-то лучше?

// usage
public class test
    {
    public int field1;
    public string field2;
    public bool field3;
    public string[] field4;
    public IDictionary<string,object> field5 { get; set; }

    public static IDictionary<string,object> dynamic()
        {
        return new Dictionary<string,object>{
            { "field1", 2 },
            { "field2", "string" },
            { "field3", true },
            { "field4", new[] { "id3", "id4", "id5" } },
            { "field5", new Dictionary<string,object>{ { "id1", "" } } }
            };
        }
    }

...
var r = new dynamic_data_serializer<test>().create( test.dynamic() );
...

// 
public class dynamic_data_serializer< T >
    {
    public T create( object obj )
        {
        var result = default(T);
        if ( obj == null )
            return result;

        var ttype = typeof(T);
        var objtype = obj.GetType();
        if ( ttype.IsAssignableFrom( objtype ) ) {
            result = (T)obj;
            return result;
            }

        if ( ttype.IsClass ) { // custom classes, array, dictionary, etc.
            result = Activator.CreateInstance<T>();

            if ( objtype == typeof(IDictionary<string,object>) || 
                    objtype == typeof(Dictionary<string,object>) ) {
                var obj_as_dict = obj as IDictionary<string,object>; 
                var fields = ttype.GetFields();
                if ( fields.Length > 0 )
                    set_fields_from( result, fields, obj_as_dict );

                var properties = ttype.GetProperties();
                if ( properties.Length > 0 )
                    set_properties_from( result, properties, obj_as_dict );
                }
            }    
        return result;
        }

    private void set_fields_from( T _this_, FieldInfo[] fields, IDictionary<string,object> obj ) {
        foreach ( var fld in fields ) {
            var v = find( obj, fld.Name );
            if ( v != null ) {
                var mobj = call_deserialize( fld.FieldType, v );
                fld.SetValue( _this_, mobj );
                }
            }
        }

    private void set_properties_from( T _this_, PropertyInfo[] properties, IDictionary<string,object> obj ) {
        foreach ( var prop in properties ) {
            var v = find( obj, prop.Name );
            if ( v != null ) {
                var mobj = call_deserialize( prop.PropertyType, v );
                prop.SetValue( _this_, mobj, null );
                }
            }
        }

    private object find( IDictionary<string,object> obj, string name ) {
        foreach ( var kv in obj )
            if ( string.Compare( kv.Key, name, true ) == 0 )
                return kv.Value;
        return null;
        }

    private object call_deserialize( Type des_type, object value ) {
        var gtype = typeof(dynamic_data_serializer<>);
        Type desz_type = gtype.MakeGenericType( new[]{ des_type } );
        object desz = Activator.CreateInstance( desz_type );
        var method_type = desz_type.GetMethod( "create" ); 
        return method_type.Invoke( desz, new[]{ value } );
        }
    }
}

8
задан AC. 1 March 2010 в 02:10
поделиться

3 ответа

DataContractJsonSerializer работает слишком медленно, но вы используете отражение? Если вам нужно десериализовать множество объектов, я бы рекомендовал использовать скомпилированные лямбда-выражения вместо отражения. Лямбда может устанавливать только свойства, а не поля (по крайней мере, в .Net 3.5), поэтому вам, возможно, придется настроить классы, в которых вы его используете, но это того стоит, потому что это примерно в 1000 раз быстрее.

Вот функция, которая создает установщик свойства с учетом типа и PropertyInfo для свойства, которое нужно установить:

    static Action<object, TValue> MakeSetter<TValue>(Type tclass, PropertyInfo propInfo)
    {
        var t = lambda.Expression.Parameter(typeof(object), "t");
        var v = lambda.Expression.Parameter(typeof(TValue), "v");
        // return (t, v) => ((tclass)t).prop = (tproperty)v
        return (Action<object, TValue>)
            lambda.Expression.Lambda(
                lambda.Expression.Call(
                    lambda.Expression.Convert(t, tclass),
                    propInfo.GetSetMethod(),
                    lambda.Expression.Convert(v, propInfo.PropertyType)),
                t,
                v)
            .Compile();
    }

У вас будет словарь установщиков для каждого класса, и всякий раз, когда вам нужно установить свойство класса, вы должны найти установщик для этого свойства в словаре и вызвать его с присвоенным значением, например: setters [propName] (_ this_, value);

2
ответ дан 6 December 2019 в 00:55
поделиться

Я мог бы предложить FormatterServices.PopulateObjectMembers , за исключением a: это все еще медленный AFAIK, и b: я пробовал это (ниже), и мне кажется, что мне нужно создать исключение для свойства (не знаю почему; не смотрел слишком глубоко). Другим вариантом может быть Выражение , но вы действительно не хотите выполнять Compile каждый раз (лучше сделать это только один раз и кэшировать, но это требует известного формата).

public T create(object obj)
{  // simplified for illustration
    var bindings = obj as IDictionary<string, object>;
    Type type = typeof(T);
    var func = Expression.Lambda<Func<T>>(Expression.MemberInit(
        Expression.New(type),
        from pair in bindings
        let member = type.GetMember(pair.Key).Single()
        select (MemberBinding)Expression.Bind(member, Expression.Constant(pair.Value))));
    return func.Compile().Invoke();
}

Наконец, вы можете кэшировать набор предварительно скомпилированных установщиков Action (с ключом для имени члена). На самом деле это, вероятно, ваш лучший выбор. Свойства просты (вы используете Delegate.CreateDelegate) - для полей может потребоваться DynamicMethod - но если вы не можете предсказать макет заранее, это будет иметь наименьшие накладные расходы.

Для подхода с ключом / IL (вы не станете быстрее):

public class dynamic_data_serializer<T>
{
    public T create(object obj)
    {
        T inst = Activator.CreateInstance<T>();
        var bindings = obj as IDictionary<string, object>;
        foreach (var pair in bindings)
        {
            setters[pair.Key](inst, pair.Value);
        }
        return inst;
    }
    private static readonly Dictionary<string, Action<T, object>> setters;
    static dynamic_data_serializer()
    {
        setters = new Dictionary<string, Action<T, object>>(StringComparer.Ordinal);
        foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) {
            setters.Add(prop.Name, CreateForMember(prop));
        }
        foreach (FieldInfo field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)) {
            setters.Add(field.Name, CreateForMember(field));
        }
    }
    static Action<T, object> CreateForMember(MemberInfo member)
    {
        bool isField;
        Type type;
        switch (member.MemberType) {
            case MemberTypes.Property:
                isField = false;
                type = ((PropertyInfo)member).PropertyType;
                break;
            case MemberTypes.Field:
                isField = true;
                type = ((FieldInfo)member).FieldType;
                break;
            default:
                throw new NotSupportedException();
        }
        DynamicMethod method = new DynamicMethod("__set_" + member.Name, null, new Type[] { typeof(T), typeof(object) });
        ILGenerator il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        if(type != typeof(object)) {
            il.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);
        }
        if (isField) {il.Emit(OpCodes.Stfld, (FieldInfo)member);}
        else { il.EmitCall(OpCodes.Callvirt, ((PropertyInfo)member).GetSetMethod(), null);  }

        il.Emit(OpCodes.Ret);
        return (Action<T, object>)method.CreateDelegate(typeof(Action<T, object>));
    }
}
1
ответ дан 6 December 2019 в 00:55
поделиться

DataContractJsonSerializer

Зачем вам создавать собственный сериализатор, а не использовать DataContractJsonSerializer?

РЕДАКТИРОВАТЬ

Если DataContractJsonSerializer вам не подходит, вы можете попробовать JSON.Net . Эффективная реализация сериализатора - непростая задача, существует множество подводных камней и особых случаев, в которые вы, возможно, не захотите попасть. Кстати, в вашем примере кода интенсивно используется медленная рефлексия, я сомневаюсь, что она будет работать лучше, чем DataContractJsonSerializer или JSON.Net.

0
ответ дан 6 December 2019 в 00:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: