Вызов Invoke MethodBase для конструктора (отражение) - PullRequest
3 голосов
/ 30 марта 2010

Прежде всего, извините, если об этом уже спрашивали. Я провел довольно сложный поиск и ничего подобного не нашел, но, возможно, что-то упустил.

А теперь к вопросу: я пытаюсь вызвать конструктор через отражение, но безуспешно. По сути, у меня есть объект, который я хочу клонировать, поэтому я ищу конструктор копирования по его типу и затем хочу вызвать его. Вот что у меня есть:

public Object clone(Object toClone) {
     MethodBase copyConstructor = type.GetConstructor(
         new Type[] { toClone.GetType() });
     return method.Invoke(toClone, new object[] { toClone }); //<-- doesn't work
}

Я вызываю вышеуказанный метод примерно так:

List<int> list = new List<int>(new int[] { 0, 1, 2 });
List<int> clone = (List<int>) clone(list);

Теперь обратите внимание, что метод invoke, который я использую, это invoke MethodBase. ConstructorInfo предоставляет метод invoke, который работает, если вызывается так:

return ((ConstructorInfo) method).Invoke(new object[] { toClone });

Однако я хочу использовать метод MethodBase, потому что в действительности вместо поиска конструктора копирования каждый раз я буду хранить его в словаре, а словарь содержит как методы, так и конструкторы, так что это Dictionary<MethodBase>, а не Dictionary<ConstructorInfo>. Конечно, я мог бы привести к ConstructorInfo, как и выше, но я бы предпочел избежать приведения и использовать метод MethodBase напрямую. Я просто не могу определить правильные параметры.

Любая помощь? Большое спасибо.


EDIT

Benjamin
Большое спасибо за ваши предложения. На самом деле я делал именно то, что вы предлагаете в вашем втором редактировании, за исключением (и это большое «кроме») мой словарь был где

class ClonerMethod {

    public MethodBase method;
    public bool isConstructor;

    ...

    public Object invoke(Object toClone) {
        return isConstructor ?
            ((ConstructorInfo) method).Invoke(new object[] { toClone }) : //<-- I wanted to avoid this cast
            method.Invoke(toClone, null);
    }

}

А потом я позвонил ClonerMethod 1033 * на то, что нашел в словаре. Я не добавил код, который касается всего этого, потому что я искал ответ только о том, как вызвать Invoke для ConstructorInfo с использованием MethodBase метода Invoke, поэтому я не хотел добавлять ненужные информация и слишком много кода для вас, ребята, чтобы прочитать. Тем не менее, мне намного больше нравится ваше использование Func<,>, поэтому я перехожу на это. Хорошим дополнением также является создание общего метода Clone, но в моем случае вызывающая сторона не знает тип объекта, поэтому вместо этого я оставлю его не универсальным.

Я не знал о Func<,>, и если бы я знал о лямбда-операторе, я забыл (мне действительно не нужно было что-то подобное раньше), так что я действительно многому научился из вашего ответа. Я всегда люблю изучать новые вещи, и это очень пригодится в будущем, поэтому большое спасибо! :)

1 Ответ

4 голосов
/ 30 марта 2010

Если вы знаете, что объект имеет такой конструктор, вы подумали об использовании этой перегрузки Activator.CreateInstance вместо?


Обновление: у вас уже есть каскадный поиск для MethodInfo / MethodBase, и вы сохраняете их -> Вы не хотите / не можете использовать Activator.

В этом случае я не вижу способа делать то, что вы хотите, без актерского состава. Но - возможно, вы могли бы изменить архитектуру для хранения Dictionary<Type, Func<object, object>> и добавить вместо этого эти Func<> экземпляры. Делает код вызова приятнее (я полагаю) и позволит вам выполнить это приведение один раз:

// Constructor
dictionary.Add(type,
  source => ((ConstructorInfo) method).Invoke(new object[] {source})
);

// Clone
dictionary.Add(type,
  source => method.Invoke(source, new object[]{})
);

На самом деле, поскольку вы заботитесь только о разнице между конструктором и обычным методом на том самом месте, где вы их захватываете, вам вообще не понадобится приведение, не так ли?

// Constructor 2
dictionary.Add(type,
  source => yourConstructorInfo.Invoke(new object[] {source})
);

Если я что-то упустил (вполне возможно, конечно), это могло бы решить проблему, выполнив это один раз на определяющей стороне забора, и вызывающему абоненту не нужно будет возражать, если это конструктор или нет?


В последний раз, тогда я собираюсь остановить редактирование спама. Мне было скучно, и я придумал следующий код. Это то, что вы пытаетесь достичь?

public class Cloner {
    private readonly IDictionary<Type, Func<object, object>> _cloneMap =
            new Dictionary<Type, Func<object, object>>();

    public T Clone<T>(T source) {
        Type sourceType = source.GetType();
        Func<object, object> cloneFunc;

        if (_cloneMap.TryGetValue(sourceType, out cloneFunc)) {
            return (T)cloneFunc(source);
        }

        if (TryGetCopyConstructorCloneFunc(sourceType, out cloneFunc)) {
            _cloneMap.Add(sourceType, cloneFunc);
            return (T)cloneFunc(source);
        }

        if (TryGetICloneableCloneFunc(sourceType, out cloneFunc)) {
            _cloneMap.Add(sourceType, cloneFunc);
            return (T)cloneFunc(source);
        }

        return default(T);
    }

    private bool TryGetCopyConstructorCloneFunc(Type type, 
                    out Func<object, object> cloneFunc) {
        var constructor = type.GetConstructor(new[] { type });
        if (constructor == null) {
            cloneFunc = source => null;
            return false;
        }
        cloneFunc = source => constructor.Invoke(new[] { source });
        return true;
    }

    private bool TryGetICloneableCloneFunc(Type type,
                    out Func<object, object> cloneFunc) {
        bool isICloneable = typeof(ICloneable).IsAssignableFrom(type);
        var cloneMethod = type.GetMethod("Clone", new Type[] { });
        if (!isICloneable || (cloneMethod == null)) {
            cloneFunc = source => null;
            return false;
        }
        cloneFunc = source => cloneMethod.Invoke(source, new object[] {});
        return true;
    }
}
...