Использование IL Emit для замены Activator.CreateInstance

У меня есть класс, реализующий интерфейс, который имеет конструктор с несколькими параметрами и статическую отсортированную коллекцию. Этот класс является базовым классом, который имеет множество унаследованных классов.

internal class SCO : IVotable
{
    public SCO(SPListItem item, List<Vote> votes)
    {
        //Initialize Object
    }

    public static List<T> SortedCollection<T>(SPListItemCollection items, ListSortType sortType, List<Vote> votes) where T : IVotable
    {
        var returnlist = new List<T>();
        Type genericType = typeof(T);
        for (int i = 0; i < items.Count; i++) { returnlist.Add((T)Activator.CreateInstance(genericType, new object[] { items[i], votes })); }
        switch (sortType)
        {
            case ListSortType.Hot:
                returnlist.Sort((p1, p2) => p2.HotScore.CompareTo(p1.HotScore));
                break;
            case ListSortType.Top:
                returnlist.Sort((p1, p2) => p2.VoteTotal.CompareTo(p1.VoteTotal));
                break;
            case ListSortType.Recent:
                returnlist.Sort((p1, p2) => p2.CreatedDate.CompareTo(p1.CreatedDate));
                break;
        }
        return returnlist;
    }
}

Это позволяет мне делать следующее с любым дочерним классом:

List<ChildClass> sortedClassList = ChildClass.SortedCollection<ChildClass>(listItems, sortType, votes);

Моя текущая зависимость от Activator.CreateInstance меня беспокоит, так как это примерно в 100 раз медленнее, чем непосредственное использование Emit IL. Я читал несколько статей об Emit IL, и это решение кажется фантастическим.

Однако я не могу заставить его работать. Когда я пытаюсь создать экземпляр ILGenerator gen =, он говорит мне: «Не удается получить доступ к методу нестатического поля в статическом контексте».

Мой класс не является статическим, как и мои конструкторы, а статический список, показанный ниже, еще не взаимодействует с Emit. Как мне заставить это работать?

Код до сих пор:

internal class SCO : IVotable
{
    //Properties emittied
    static ConstructorInfo ctor = typeof(SCO).GetConstructors()[1];
    delegate SCO SCOCtor(SPListItem item, List<Vote> votes);
    static SCOCtor SCOCtorDelegate;

    DynamicMethod method = new DynamicMethod("CreateInstance", typeof (SCO),
                             new Type[] {typeof (SPListItem), typeof (List<Vote>)});

    ILGenerator gen = method.GetILGenerator(); //Error here
    //"Cannot access non-static field 'method' in static context"

    private static SCO CreateInstance(SPListItem item, List<Vote> votes)
    {
        return SCOCtorDelegate(item, votes);
    }
}

Блог для справки: http://ayende.com/blog/3167/creating-objects-perf-implications


person Wesley    schedule 27.08.2012    source источник
comment
Дочерние классы известны во время компиляции?   -  person Marcelo De Zen    schedule 27.08.2012
comment
да. В качестве иллюстрации использовался ChildClass, но у меня есть, например, класс Post : SCO, который можно вызвать с помощью: List<Post> sortedPosts = Post.SortedCollection<Post>(listItems, sortType, votes);   -  person Wesley    schedule 27.08.2012
comment
Кажется, что каждый 10-й вопрос касается создания клона SO для развлечения.   -  person usr    schedule 27.08.2012
comment
Проблема с вашим кодом IL заключается в том, что вы не можете получить доступ к другим полям экземпляра из инициализатора поля экземпляра. Вы должны использовать конструктор (возможно, статический конструктор) для такой инициализации.   -  person svick    schedule 28.08.2012


Ответы (3)


У меня есть сменная замена для Activator, использующая генерацию IL, размещенная здесь на КодПлекс. Вы также можете получить его через Nuget здесь (включая один исходный файл, без сборок).

Исходный код FasterActivator находится здесь.

Использование похоже на то, что описано ниже.

private static readonly Dictionary<Type, DynamicCreationDelegate> _cachedCreationDelegates = new Dictionary<Type, DynamicCreationDelegate>();

private static DynamicCreationDelegate CreateOrGet(Type typeToCreate)
{
    DynamicCreationDelegate result = null;

    if (!_cachedCreationDelegates.TryGetValue(typeToCreate, out result))
    {
        result = FastActivator.GenerateDelegate(typeToCreate, 
        /* List of types that make up the constructor signature of the type being constructed */
        typeof(SPListItem), typeof(List<Vote>));
        _cachedCreationDelegates.Add(result);
    }

    return result;
}

// Usage 
for (int i = 0; i < items.Count; i++) 
{ 
    var creationDelegate = CreateOrGet(typeof(genericType));
    returnlist.Add((T)creationDelegate(new object[] { items[i], votes })); 
}

О, и вот универсальная версия, которая должна быть быстрее.

private static readonly Func<SPListItem, List<T>, T> _creationFunc;
private static Func<SPListItem, List<T>, T> CreateOrGetFunc()
{
    if (!_creationFunc == null)
        _creationFunc = FastActivator.GenerateFunc<Func<SPListItem, List<T>, T>>(/* IL generator knows all type info from generic params now */);

    return _creationFunc;
}

// Usage
for (int i = 0; i < items.Count; i++) 
{ 
    var creationFunc = CreateOrGetFunc();
    returnlist.Add(creationFunc(items[i], votes )); 
}

Надеюсь это поможет!

person Ani    schedule 27.08.2012
comment
Если вы не возражаете, у меня небольшие проблемы с созданием экземпляра Func с несколькими параметрами в цикле. Как бы вы написали это в верхнем блоке кода SortedCollection - person Wesley; 27.08.2012
comment
Выдает ошибку. Я мог бы просто открыть новый тикет и опубликовать URL-адрес здесь, чтобы разобраться. - person Wesley; 27.08.2012
comment
.Post__041c49eec0a6466da11894b0455b1162(Microsoft.SharePoint.SPListItem, System.Collections.Generic.List`1‹MonkeySphere.Vote›) - person Wesley; 27.08.2012
comment
stackoverflow.com/questions/12148799 / - person Wesley; 28.08.2012

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

Использование примерно такое:

// suppose you want to call the constructor for this class  
// but generalizing the return to ISomeInterface
public class AClass : ISomeInterface
{
   public class(int intParam, String stringParam) { }
}

// construct the factory method Func<int, string, ISomeInterface>
var createAClassInstance = ReflectionHelper
   // return type + constructor params
   .CreateFactoryMethod<ISomeInterface, int, string>(typeof(AClass));

var instance = createAClassInstance(10, "hello constructor");
person Marcelo De Zen    schedule 27.08.2012
comment
Если это работает без библиотеки и достаточно быстро, я бы подумал об изменении ответа. - person Wesley; 28.08.2012
comment
System.Collections.Concurrent отсутствует, и я не рассматриваю его как ссылку, которую можно добавить. - person Wesley; 28.08.2012
comment
Это один файл, без библиотеки - возможно, когда-нибудь :) - person Marcelo De Zen; 28.08.2012
comment
Этот класс является классом .net4, измените его на Dictionary‹›, если вы используете предыдущую версию фреймворка... - person Marcelo De Zen; 28.08.2012
comment
На самом деле вам нужно будет изменить другой метод два. Какую версию фреймворка вы используете? - person Marcelo De Zen; 28.08.2012

Если вы готовы допустить некоторое повторение, то вместо того, чтобы иметь дело с IL (который, как вы обнаружили, может быстро усложниться), вы можете использовать делегатов. Что-то типа:

internal class SCO : IVotable
{
    protected static List<T> SortedCollection<T>
        (SPListItemCollection items, ListSortType sortType, List<Vote> votes,
        Func<SPListItem, List<Vote>, T> factory) where T : IVotable
    {
        var returnlist = new List<T>();
        Type genericType = typeof(T);
        for (int i = 0; i < items.Count; i++)
            returnlist.Add(factory(items[i], votes));

        // etc.
    }
}

class ChildClass : SCO
{
    public static List<ChildClass> SortedCollection
        (SPListItemCollection items, ListSortType sortType, List<Vote> votes)
    {
        return SCO.SortedCollection<ChildClass>(
            items, sortType, votes, (i, vs) => new ChildClass(i, vs));
    }
}

Выполнение этого таким образом должно быть очень быстрым, возможно, даже немного быстрее, чем при использовании IL-эмиссии.

person svick    schedule 28.08.2012
comment
Интересно. Я попробую. - person Wesley; 29.08.2012