Вызвать универсальный метод с универсальным типом из System.Type

ниже приведен пример кода и вопрос, обратите внимание, что я НЕ могу использовать С# 4.0 и динамическое ключевое слово.

static class TestClass
{
    static void Main(string[] args)
    {
        Object o = "Previous value";
        Test(ref o);
        Trace.WriteLine(o);
    }

    static public void Test<T>(ref T obj)
    {
        //  The goal is to somehow invoke Test2 with the real type of obj, i.e the type in obj.GetType() 

        //  1st try:
        Test2(ref obj); // This doesn't work because the type in Test2 will be the same as T here.

        //  2nd try:
        MethodInfo mi = typeof(TestClass).GetMethod("Test2");
        mi = mi.MakeGenericMethod(new Type[] { obj.GetType() });
        mi.Invoke(null, new Object[] { obj }); // obj is no longer by reference so we need to store the object array and copy back the result after the call

        //  3rd try, successful implementation by the smartest mind of stack overflow :)
    }

    static public void Test2<T>(ref T s)
    {
        if (typeof(T) == typeof(String))
        {
            s = (T)(Object)"Hello world!";
        }
    }
}

Я также попробовал еще несколько методов, используя Delegate.CreateDelegate, но безуспешно. Это вообще возможно?

Редактировать: я не боюсь использовать динамический метод (и ассемблер MSIL), но мои знания в этой области очень ограничены.

Edit2: Вот пример, который ближе к тому, что я действительно пытаюсь сделать:

public static class TypeHandler<T>
{
    public delegate void ProcessDelegate(ref T value);

    public static readonly ProcessDelegate Process = Init();

    private static ProcessDelegate Init()
    {
        //  Do lot's of magic stuff and returns a suitable delegate depending on the type
        return null;
    }
}


static class TestClass
{
    static public void Main(string[] args)
    {
        Object o = "Previous value";
        Test(ref o);
        Trace.WriteLine(o);
    }

    static public void Test<T>(ref T obj)
    {
        if (obj is T)
        {
        //  Optimized, common case
            TypeHandler<T>.Process(ref obj);
            return;
        }
        Type t = obj.GetType();
        //  How to call the delegate found in TypeHandler<t>.Process ? (I can get delegate but I can't call it).


    }

}

person eq_    schedule 02.12.2011    source источник
comment
похоже, вы не должны пытаться использовать дженерики. вы все равно используете отражение, так что просто опирайтесь на это   -  person Adam Ralph    schedule 02.12.2011
comment
Тот факт, что вы проверяете тип своего универсального параметра, является красным флагом. Возможно, дженерики здесь неуместны.   -  person cadrell0    schedule 02.12.2011


Ответы (5)


Обновление 3: хорошо, так как вас устраивает уродливое решение, вы можете проверить недокументированные __refvalue и __makeref ключевые слова.


Похоже, ваша проблема заключается в том, что вы хотите иметь возможность указать тип параметра ref object для преобразования или изменения.

Проблема в том, что вы не можете просто произвольно присвоить переменную любого типа T, например, string. Таким образом, вам нужно будет передать ref object или ref string, чтобы это работало вообще.

Мне кажется, что oberfreak зафиксировал то, чего вы пытались достичь (первоначально я не заметил, что вы инициализировали o как string и поэтому явно хотел, чтобы его фактический тип влиял на поведение функции Test2). Его ответ имеет правильный подход для вас.

Обновление: вы упоминаете в комментарии, что пытаетесь добиться динамического поведения, которого можно добиться с помощью словаря. Я предполагаю, что это выглядит примерно так?

Обновление 2: обновлен этот пример на основе вашего обновленного примера.

public static class TypeHandler // note: get rid of generic T parameter
{
    delegate void ProcessDelegate(ref object obj); // again, not generic

    static Dictionary<Type, ProcessDelegate> processors = new Dictionary<Type, ProcessDelegate>()
    {
        { typeof(string), (ref object obj) => { obj = "Hello, world!"; } }
        // etc.
    };

    public static void Process(ref object obj)
    {
        processors[obj.GetType()].Invoke(ref obj);
    }
}

Это должно работать. Но вы не можете добиться того же самого с дженериками, потому что нет способа сделать это (как вы знаете):

//          not allowed
//               |
//          -----------
//         |           |
TypeHandler<o.GetType()>.Process(ref o);

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

person Dan Tao    schedule 02.12.2011
comment
Это несколько похоже на то, что я считаю, поскольку моя реальная проблема всегда не так проста, как пример. Типы, которые мне нравится поддерживать (в Test2), не являются статическими, как в вашем примере, скорее это похоже на делегат Dictionary of Type to TypeHandler, тогда у меня есть кто-то, кто регистрирует, какие типы обрабатывать и с каким делегатом. Проблема в том, что я пытаюсь использовать другой подход, чем фактически использовать словарь, а именно наличие статического универсального класса, который имеет статическое поле с делегатом (инициализируется с помощью статической функции инициализации). Я надеялся увидеть улучшение скорости поиска по словарю. - person eq_; 02.12.2011
comment
Но проблема в том, что вы хотите, чтобы этот класс вел себя определенным образом в зависимости от фактического типа переменной. В этом случае o, например, является string. Но это не то, на чем основаны дженерики, а не объявленные типы. В данном случае это означает object вместо o. Вот почему мне лично кажется, что вы лаете не по тому дереву с дженериками; и я чувствую, что ответ oberfreak на самом деле указывает вам правильное направление. - person Dan Tao; 02.12.2011
comment
Да, я почему-то согласен с вами, что дженерики, вероятно, не являются правильным способом ведения дел. Я обновил свой исходный вопрос примером, более близким к тому, что я пытаюсь сделать. - person eq_; 02.12.2011
comment
Концептуально это то, что мне нужно, проблема в том, что я не хочу, чтобы Test1 принимал ссылку на объект (один), он должен принимать ссылку на любой тип (или мне нужно снова платить копию типов значений). Я знаю, что если есть решение, которое не требует копии, оно не будет красивым, но ведь это же не конкурс красоты, не так ли? ;) - person eq_; 02.12.2011
comment
@ user1077451: Дело не в том, чтобы быть красивым. Речь идет о производительности. Вы просто не сможете сэкономить на производительности, вводя универсальные шаблоны, а затем добавляя большое количество отражений. Накладные расходы на отражение абсолютно затмят крошечный прирост производительности, который вы получаете, избегая копий значений. - person Dan Tao; 02.12.2011
comment
Верно, но я надеялся, что смогу кэшировать результирующий вызов метода в делегате, заплатив цену за путь отражения только один раз за пару Тип/Тип (что не так уж и плохо). У меня есть около 100 тысяч вызовов с ~ 200 различными типами, так что это показалось хорошей идеей, но, возможно, я ошибаюсь, новичок в C#/управляемом коде. - person eq_; 02.12.2011
comment
Эти ключевые слова выглядят интересно, я могу, по крайней мере, использовать их, чтобы избежать бокса, но все еще не уверен, могу ли я использовать их для решения текущей проблемы. Спасибо, в любом случае! - person eq_; 02.12.2011

Ваш комментарий выглядит так, как будто вы уже понимаете, как это сделать:

MethodInfo mi = typeof(TestClass).GetMethod("Test2");
mi = mi.MakeGenericMethod(new Type[] { obj.GetType() });
object[] args = new object[] { obj };
mi.Invoke(null, args);
obj = (T) args[0];

Это действительно просто превращение вашего комментария в код. Это как-то не делает то, что вы хотите?

person Jon Skeet    schedule 02.12.2011
comment
Да, я знаю, как это сделать (так я делаю сегодня), но это упражнение по оптимизации, и я хотел бы избежать копирования по значению (для типов значений). - person eq_; 02.12.2011
comment
@ user1077451: Это говорит о том, что для вас важна производительность. Каковы ваши цели по производительности и чего достигает текущий код? (Я подозреваю, что динамические методы могут улучшить ситуацию, но я бы не стал туда обращаться, если вам действительно не нужно.) - person Jon Skeet; 02.12.2011
comment
Хороший вопрос! У меня нет фиксированной цели, но моя текущая рабочая реализация была создана как доказательство концепции, она работает отлично, но медленно. Сейчас я переписываю, пытаясь использовать все приемы из книги (моя ограниченная книга), чтобы добиться того же самого как можно быстрее, я был бы счастлив, если бы я мог заставить вещи работать на 20% от текущей производительности. В этой итерации я также добавил больше гибкости в систему, поэтому я не ожидаю, что попаду в цель в этой итерации. В следующем раунде я рассмотрю возможность использования динамических методов и MSIL, но сначала я хотел закрепить инфраструктуру. - person eq_; 02.12.2011
comment
То есть: первая итерация: каждый тип обрабатывается в одной функции с множеством операторов if/else, вызывающих виртуальные функции здесь и там + в некоторых случаях с использованием отражения. Вторая итерация: оптимизируйте путь кода для исходного типа (т. е. используя поиск типа в код). Третья итерация: перепишите код для каждого случая с помощью MSIL (поскольку существует слишком много случаев, которые удобно обрабатывать вручную). - person eq_; 02.12.2011
comment
@user1077451: Сколько вашего текущего времени вы тратите на этот кусок кода? - person Jon Skeet; 02.12.2011
comment
Хватит ;) Поверьте надо оптимизировать. Я занимаюсь разработкой игр, а затем более продвинутых систем как профессия более пятнадцати лет, поэтому я знаю, на что не тратить время :) Я новичок в .net/C#, хотя многие вещи пришли из мира C++ которые так легко сделать в неуправляемом коде, чрезвычайно сложны в управляемом коде. Я знаю, что переписывание всего в неуправляемом коде, вероятно, дало бы мне 10-кратный прирост скорости, но за этим проектом стоит около года разработки из моего банкомата, поэтому идти по этому пути - большое нет-нет (и мне не разрешено использовать все равно неуправляемый). - person eq_; 02.12.2011

Главный вопрос, на мой взгляд, что ты хочешь делать?

Если вы просто хотите присвоить строку ссылочному объекту, вы можете попробовать следующее:

Генерики могут быть определены во время выполнения, но это не очень удобно и должно выполняться путем отражения... по моему мнению, это "нет", но просто мнение

Попробуйте использовать object.GetType(), чтобы получить текущий тип объекта.

static class TestClass {
    static void Main(string[] args) {
        Object o = "Previous value";
        Test(ref o);
        Console.WriteLine(o);
        Console.ReadLine();
    }

    static public void Test<T>(ref T obj) {
        Object o = (Object)obj;
        Test2(ref o);
        obj = (T)o;
    }

    static public void Test2(ref object s) {
        if (s.GetType().Equals(typeof(String))) {
            s = "Hello world!";
        }
    }
}
person oberfreak    schedule 02.12.2011
comment
Это даже не компилируется и не близко к тому, что мне нужно, если бы мне нужно было работать только с типами Object/String, это было бы достаточно просто. Мне нужно, чтобы он работал для любого типа для любого типа, который реализует другой тип. - person eq_; 02.12.2011
comment
@ user1077451: Как вы планируете это сделать, не используя гигантскую серию операторов if/else? Кажется, вы хотите добиться чего-то с помощью дженериков, в чем они просто не хороши. (Обобщения дают вам одно и то же поведение для различных типов; то, что вам нужно, выглядит как различное поведение для разных типов.) - person Dan Tao; 02.12.2011
comment
я обновил код, извините за эту ошибку! Теперь он компилируется правильно. Почему этот код не решает вашу проблему? вы можете проверить тип объекта в Test2 и решить, какой объект назначить? - person oberfreak; 02.12.2011
comment
Я не хочу заставлять Test принимать только ссылку на объект, он должен работать с любым типом, иначе мне нужно скопировать все типы значений (после изменения объекта). Объект o = ОбъектЛюбогоТипа; Тест (ссылка о); объект любого типа = о; - person eq_; 02.12.2011
comment
Да, но будет ли он по-прежнему печатать Hello world? ;) Вам нужно добавить obj = (T)(Object)o; в конце метода испытаний... - person eq_; 02.12.2011

Правильным способом реализации метода Test2 будет

static public void Test2<T>(ref T s)
    {
        if (s is string)
        {
            s = (T)(Object)"Hello world!";
        }
}

Или я что-то здесь упускаю?

person matts    schedule 02.12.2011

Если вы можете изменить ref на обычный возврат, вы можете массово читерить в 4.0 через dynamic:

dynamic foo = obj;
Test(foo);

то есть сейчас:

Test<TheActualTypeOfObj>(obj);

Полный пример:

static void Main(string[] args)
{
    object o = "Previous value";
    o = Test2((dynamic)o);
    Trace.WriteLine(o);
}

static public T Test2<T>(T s)
{
    if (typeof(T) == typeof(string))
    {
        s = (T)(object)"Hello world!";
    }
    return s;
}

который пишет "Привет, мир!"

person Marc Gravell    schedule 02.12.2011
comment
Я знаю, что это просто в 4.0, но, как я уже говорил, я не могу использовать его для этого проекта :( - person eq_; 02.12.2011
comment
Однако он действительно сказал, что не может использовать ключевое слово dynamic. - person Dan Tao; 02.12.2011
comment
@user1077451 user1077451 хорошо, не заметил - извините. Я оставлю это здесь, на всякий случай, если это поможет кому-то еще с похожей проблемой и доступом к 4.0. - person Marc Gravell; 02.12.2011