使用System.Type中的泛型类型调用泛型方法

3

以下是一个代码示例和问题,请注意我不能使用C# 4.0和dynamic关键字。

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汇编器),但我在这个领域的知识非常有限。 编辑2:这里有一个更接近我真正尝试做的例子:
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).


    }

}

听起来你不应该尝试使用泛型。反正你已经在使用反射了,所以就依靠它吧。 - Adam Ralph
1
检查泛型参数的类型是一个警示信号。也许在这里使用泛型不是正确的选择。 - cadrell0
5个回答

2

在我看来,主要问题是你想做什么?

如果你只想将一个字符串赋值给一个引用对象,你可以尝试这样做:

泛型可以在运行时定义,但这并不是很方便,必须通过反射来完成...在我看来,这是一个"不可行"的选择,但只是我的意见。

尝试使用 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!";
        }
    }
}

这甚至无法编译,并且与我所需求的相距甚远。如果我只需要处理Object/String类型,那么就足够简单了。 我需要它能够适用于任何实现另一种类型的任何类型。 - eq_
1
@user1077451:如果不使用一大堆的if/else语句,你打算如何实现这个功能呢?你似乎想通过泛型来实现一些它们并不擅长的事情。(泛型为各种类型提供了相同的行为;而你所追求的似乎是对不同类型提供不同的行为。) - Dan Tao
我更新了代码,对于那个错误很抱歉! 现在它可以正确编译。 为什么这段代码不能解决你的问题?你可以检查Test2中对象的类型并决定要分配哪个对象。 - oberfreak
我不想强制Test只接受对象的引用,它应该适用于任何类型,否则在对象更改后,我需要复制所有值类型。 对象o = 任何类型的一个对象; 测试(ref o); 任何类型的一个对象 = o; - eq_
是的,但它仍然会打印“Hello world”吗?;) 你需要在Test方法的末尾添加obj = (T)(Object)o; - eq_

2

根据您的评论,看起来您已经了解如何操作:

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

这实际上就是将你的评论转换为代码。这样做不会有什么问题吧?

是的,我知道可以这样做(这是我今天的做法),但这是一个优化练习,我想避免按值复制(对于值类型)。 - eq_
@user1077451:这表明性能对你很重要。你的性能目标是什么,当前的代码实现了什么?(我怀疑动态方法可以改善情况,但除非你确实必须这样做,否则我不会那样做。) - Jon Skeet
好问题!我没有固定的目标,但我的当前工作实现是作为概念验证创建的,它可以完美地工作,但速度很慢。我现在正在重写代码,尝试使用书中的每一个技巧(我的书有限),以尽可能快地实现相同的功能,如果能够以当前性能的20%运行,我会很高兴。在这个迭代中,我还增加了系统的灵活性,因此我并不真正期望在这个迭代中达到目标。 对于下一轮,我将考虑使用动态方法和MSIL,但我首先想要解决基础设施问题。 - eq_
第一次迭代:每种类型都在单个函数中处理,有很多if/else语句,在此处调用虚拟函数+在某些情况下使用反射。 第二次迭代:根据初始类型优化代码路径(即使用Type到Code查找)。 第三次迭代:为每种情况重写代码,使用MSIL(因为手动处理的案例太多了)。 - eq_
@user1077451:你现在有多少时间花在这段代码上? - Jon Skeet
够了;) 相信我,优化是必要的。 我已经作为职业开发游戏和更高级系统超过十五年了,所以我知道不值得花时间的事情 :) 虽然我很新于 .net / C#,但我来自 C++ 的世界,许多在非托管代码中容易实现的事情,在托管代码中却极其烦人和复杂。 我知道将所有东西重写为非托管代码可能会使我获得10倍的速度提升,但这个项目已经落后于我的一年开发进度,所以走这条路是绝对不行的(而且我也不允许使用非托管代码)。 - eq_

1

更新 3:好的,既然您可以接受一个丑陋的解决方案,您可能想要查看未记录的__refvalue__makeref关键字


看起来你的问题是想要指定一个ref object参数的类型被“转换”或“更改”为什么。

问题在于,你不能随意将任何类型T的变量分配给string。因此,你需要传递一个ref objectref 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);

如果有的话,那么你就万事大吉了。但是唯一可能做到这一点的方法是使用反射,这对于像这样的东西来说既丑陋又昂贵,并且显然会打败你保持简单和确保良好性能的意图。

这与我考虑的有些相似,但实际问题并不像示例那样简单。 我想要支持的类型(在Test2中)不像你的示例那样是静态的,而更像是一种从Type到“TypeHandler”委托的字典,然后我让某人注册要处理的类型以及使用哪个委托。 问题是,我正在尝试使用另一种方法来代替实际使用字典,即使用一个具有委托静态字段(使用静态初始化函数初始化)的静态泛型类。 我希望能看到比字典查找更快的速度提高。 - eq_
但问题在于,您希望这个类根据变量的实际类型以某种方式运作。在这种情况下,o 例如是一个 string。但这不是泛型基于的东西,而是声明的类型。在这种情况下,表示为 oobject。所以对我来说,你对泛型的理解似乎有些偏离,我认为 oberfreak 的答案实际上指出了正确的方向。 - Dan Tao
那就是我需要的概念,问题在于我不想让Test1仅接受一个对象的引用,它应该接受任何类型的引用(或者我需要再次复制值类型)。 我知道如果有一种不需要复制的解决方案,它不会很漂亮,但嘿,这不是一个选美比赛,是吧? ;) - eq_
@user1077451:这不是关于美观的问题,而是关于性能的问题。你不能通过引入泛型并添加大量反射来节省性能。 反射开销绝对会使你避免值复制所获得的微小性能增益相形见绌。 - Dan Tao
这些关键字看起来很有意思,我至少可以用它们来避免装箱问题,但还不确定我是否能/如何使用它们来解决当前的问题。无论如何,还是谢谢! - eq_
显示剩余2条评论

0

实现Test2方法的正确方式是:

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

还是我漏掉了什么?


0
如果你能将 ref 改为常规返回,你就可以通过 dynamic 在 4.0 中大规模作弊。
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;
}

写下“Hello world!”的代码


我知道在4.0中很容易,但正如我所提到的,我不能在这个项目中使用它 :( - eq_
他确实说过他不能使用 dynamic 关键字。 - Dan Tao
1
@user1077451 好的,我没有注意到 - 抱歉。不过,我还是会把这个留在这里,以防其他人遇到类似的问题并且使用4.0版本时需要帮助。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接