通用函数 vs 扩展方法

3
我是一个尝试使用C#泛型的C++开发者。 我的最终目标是在C#中模拟模板特化。
换句话说,我希望为所有类型提供一个默认函数实现,并为特定类型提供一个特定实现。
我的第一次尝试是使用泛型和扩展方法,但似乎C#的泛型是在运行时解析的,这给出了这种奇怪的结果(至少对于一个C++程序员来说是奇怪的):
//any of my classes
public class MyClass
{
}

//class extension for my classes only
public static class MyClassExt
{
    public static string GetCaption(this MyClass c)
    {
        return "MyClass caption";
    }
}

//"default" extension for all
public static class objectExt
{
    public static string GetCaption(this object o)
    {
        return $"object caption {o}";
    }
}

internal class Program
{
    public static string GetCaption<T>(T t)
    {
        //Isn't it supposed to call MyClassExt.GetCaption if t is MyClass ?
        return t.GetCaption();
    }

    static void Main(string[] args)
    {
        int i = 0;
        Console.WriteLine(i.GetCaption());              //> object caption 0
        Console.WriteLine(new MyClass().GetCaption());  //> MyClass caption

        //calls GetCaption<T> with T is MyClass
        Console.WriteLine(GetCaption(new MyClass()));   //> object caption csharpconsole.MyClass
    }
}

通常在C#中处理这种情况的常见方法是什么?
编辑:
尝试实现@Enigmativity的建议,但没有成功。
//class extension for my classes only
public static class MyClassExt
{
    //My attempt for @Enigmativity suggestion
    public static string GetCaption<T>(this T t) where T : MyClass
    {
        return "MyClass caption";
    }
}

//"default" extension for all
public static class objectExt
{
    public static string GetCaption(this object o)
    {
        return $"object caption {o}";
    }
}

internal class Program
{
    public static string GetCaption<T>(T t)
    {
        //Isn't it supposed to call MyClassExt.GetCaption if t is MyClass ?
        return t.GetCaption();
    }

    static void Main(string[] args)
    {
        int i = 0;
        Console.WriteLine(i.GetCaption());              //> object caption 0
        Console.WriteLine(new MyClass().GetCaption());  //> MyClass caption
        
        //calls GetCaption<T> with T is MyClass
        Console.WriteLine(GetCaption(new MyClass()));   //> object caption csharpconsole.MyClass
    }
}

你真的需要扩展方法吗?为什么MyClass不直接拥有一个GetCaption方法呢?或许它应该是一个接口? - undefined
@Charlieface,我确实没有搞清楚。我不希望GetCaption成为MyClass的一部分,因为它属于另一个“层”。假设GetCaption仅用于UI,而MyClass与UI解耦。 - undefined
1
@LoicJourdan - 你的修改并没有实现我的建议。请查看我的回答以获取完整的代码。 - undefined
2个回答

7
我的最终目标是在C#中模仿模板特化。
老实说,我认为这不太可能有好结果。泛型并不像C++的模板,它们用于实现类似的目标,但采用了完全不同的方法。
我的第一次尝试是使用泛型和扩展方法,但似乎C#的泛型是在运行时解析的。
不,这不是真的,这就解释了你得到的结果。
当编译GetCaption<T>的代码时,编译器在不知道T的情况下解析对t.GetCaption()的调用,所以它只能调用第一个参数类型为object的扩展方法,这就是你看到的结果。
基本上,你需要记住每个泛型方法只会被编译一次,而不是针对每个类型参数都编译一次。一切都源于这个理解:你可以像平常一样看待多态性和重写,但不能像重载那样在编译时选择不同的方法。

如果我重新表达一下,C#泛型在编译后仍然是泛型吗?这是你所说的“不知道T的任何信息”吗? - undefined
2
是的,GetCaption<T> 的 IL 仍然“知道”它是泛型的,并且有一个单独的 IL 片段用于它。在编译 GetCaption<T> 时,编译器并不知道 T 将会是什么,因为对 GetCaption<T> 的使用可能是在尚未编写的代码中。 - undefined

2
如果你把扩展方法改成这样:
public static string GetCaption<T>(T t) where T : MyClass

然后它将调用MyClass扩展方法,因为编译器可以确定这是要调用的最具体的方法。
如果没有where T : MyClass,那么object扩展方法是编译时可以确定的最具体的方法。
当我使用where T : MyClass运行您的代码时,我得到的结果是:
object caption 0
MyClass caption
MyClass caption

没有它,就像你发布的那样,我得到的是:
object caption 0
MyClass caption
object caption MyClass

这是完整的工作程序。
//any of my classes
public class MyClass
{
}

//class extension for my classes only
public static class MyClassExt
{
    public static string GetCaption(this MyClass c)
    {
        return "MyClass caption";
    }
}

//"default" extension for all
public static class objectExt
{
    public static string GetCaption(this object o)
    {
        return $"object caption {o}";
    }
}

internal class Program
{
    public static string GetCaption<T>(T t) where T : MyClass
    {
        //Isn't it supposed to call MyClassExt.GetCaption if t is MyClass ?
        return t.GetCaption();
    }

    static void Main(string[] args)
    {
        int i = 0;
        Console.WriteLine(i.GetCaption());              //> object caption 0
        Console.WriteLine(new MyClass().GetCaption());  //> MyClass caption

        //calls GetCaption<T> with T is MyClass
        Console.WriteLine(GetCaption(new MyClass()));   //> object caption csharpconsole.MyClass
    }
}

嗨 @Enigmativity, 我已经尝试过这个了,但是我得到的结果和之前完全一样。 - undefined
@LoicJourdan - 请贴上你的准确代码。在回答之前,我检查了它是否有效。 - undefined
@LoicJourdan - 我已经把我的输出添加到答案中。 - undefined
嗨 @Enigmativity,我已经编辑了问题并添加了我的尝试,再次感谢。 - undefined
嗨@Enigmativity,谢谢,很抱歉回复这么晚,我离开了一段时间。 你在代码中所做的事情完全消除了对通用的GetCaption<>的需求,因为现在它被限制在了MyClass中。 目标是获得一个适用于所有类型并且可以根据类型有不同实现的唯一GetCaption(就像我们在C++模板特化中所拥有的)。 不管怎样,再次感谢,我很感激。 - undefined

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