使用反射确定C#方法是否具有关键字“override”

3

我本以为这个问题很容易得到答案,但事实并非如此。

我想知道是否有可能通过MethodInfo的实例来确定一个方法是否带有关键字“override”。

我想也许以下代码可以实现:

/// <summary> Returns whether the specified methodInfo is attributed with the keyword 'override'. </summary>
public static bool IsOverriding(this MethodInfo methodInfo)
{
    if (methodInfo == null) throw new ArgumentNullException();
    return methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType;
}

我已经成功测试了一些非虚拟,虚拟和抽象的例子,但我觉得我缺少一些场景,可能是与隐藏或泛型有关(虽然我无法想象这将如何发挥作用)。


这似乎是 https://dev59.com/t3A85IYBdhLWcg3wHvzB 的重复。 - James Michael Hare
@James,嗯...确实是一个类似的问题,但并不完全相同。我使用了“override”这个词进行搜索,不幸的是那篇帖子没有找到,可能是因为它使用了“overridden”这个词。然而,那里的答案并不适合我。 - JBSnorro
3个回答

3

我也试着找到这个东西。从问题中,你给出了使用override关键字来使IsOverriding起作用的想法。然而,对于隐藏,我试图创建new关键字的IsHiding。以下是基本的C# OOP:

  • override仅在基方法包含abstractvirtual修饰符时使用。
  • new用于隐藏具有相同名称但...
  • new不能应用于abstract基本方法,因为它会生成编译器错误。
  • 然而,有趣的部分是,如果基方法包含virtual,则还可以将new应用于方法。

有趣的部分是我们可以隐藏或覆盖virtual方法。我们知道,如果我们重写virtual方法,则GetBaseDefinition()将返回基MethodInfo。但是区分它的关键是,如果我们隐藏virtual方法,则GetBaseDefinition()将返回相同的MethodInfo而不是它的基MethodInfo

override关键字是必须的,而new仅用于抑制警告消息。因此,我们可以通过IsAbstractIsVirtualDeclaringTypeBaseType相结合来区分overridenew

    public static bool IsOverriding(this MethodInfo methodInfo)
    {
        if (methodInfo == null) throw new ArgumentNullException("methodInfo");
        return methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType;
    }

    public static bool IsHiding(this MethodInfo methodInfo)
    {
        if (methodInfo == null) throw new ArgumentNullException("methodInfo");
        if (methodInfo.DeclaringType == methodInfo.GetBaseDefinition().DeclaringType)
        {
            var baseType = methodInfo.DeclaringType.BaseType;
            if (baseType != null)
            {
                MethodInfo hiddenBaseMethodInfo = null;
                var methods = baseType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static);
                foreach (var mi in methods)
                    if (mi.Name == methodInfo.Name)
                    {
                        var miParams = mi.GetParameters();
                        var methodInfoParams = methodInfo.GetParameters();
                        if (miParams.Length == methodInfoParams.Length)
                        {
                            var i = 0;
                            for (; i < miParams.Length; i++)
                            {
                                if (miParams[i].ParameterType != methodInfoParams[i].ParameterType
                                    || ((miParams[i].Attributes ^ methodInfoParams[i].Attributes).HasFlag(ParameterAttributes.Out))) break;

                                // Simplified from:
                                //if (miParams[i].ParameterType != methodInfoParams[i].ParameterType
                                //    || (miParams[i].Attributes.HasFlag(ParameterAttributes.Out) && !methodInfoParams[i].Attributes.HasFlag(ParameterAttributes.Out))
                                //    || !(miParams[i].Attributes.HasFlag(ParameterAttributes.Out) && methodInfoParams[i].Attributes.HasFlag(ParameterAttributes.Out))) break;
                            }
                            if (i == miParams.Length)
                            {
                                hiddenBaseMethodInfo = mi;
                                break;
                            }
                        }
                    }
                if (hiddenBaseMethodInfo != null && !hiddenBaseMethodInfo.IsPrivate) return true;
            }
        }
        return false;
    }

我使用简单的继承进行了测试,并且它有效。我还没有考虑泛型方法..但是..
编辑: 我刚刚更改了上面的代码,因为我忘记了继承类型可能包含新声明的方法,这些方法不应视为新方法。IsHiding()将首先确保它具有相同的DeclaringType(它看起来像新方法),但需要通过DeclaringType.BaseType查看基本声明类型是否存在具有相同名称的方法。
请注意,由于没有BindingFlags.DeclaredOnlyGetMethod()将搜索整个基类型,因此无需递归地搜索每个基类型。BindingFlags.FlattenHierarchy用于在抽象-抽象基类中包括静态方法,例如:
public abstract class A
{
    public static void Stat() { }
}
public abstract class B : A
{
}
public class C: B
{
    public new static void Stat() { }
}

编辑:我刚刚修复了上面的IsHiding()方法,以检查基本方法重载并使用GetMethods()而不是GetMethod()来防止AmbiguousMatchException。测试过的重载与不同参数的组合一起工作,与refoutparams和可选参数混合使用。基于参数计数、参数类型及其修饰符比较重载的签名:

  • refout包含在签名中,但不能相互重载。
  • 返回类型、可选(默认值)和右侧最后一个参数上的params应被忽略。

ref通过ParameterType本身进行比较(在调试期间查看结尾'&'),不需要通过IsByRef进行比较,而out则通过Attributes标志进行比较。我使用简化的按位异或表达式,仅在其中一个属性具有Out标志使签名不同的情况下跳过循环。不要与.NET 4中的HasFlag混淆,它只想确保XOR结果中的Out位为1。


你可能白费力气了...类MethodBase有一个属性叫做IsHideBySig,它几乎完全做了你的IsHiding方法要做的事情:P抱歉:P - JBSnorro
真的吗?我只是忽略它,因为名称很容易混淆。应该像IsHideBySignature这样的东西。忘记IsHiding,因为如果方法名有多个重载,它会抛出AmbiguousMatchException异常。我将使用IsHideBySig - CallMeLaNN
1
“IsHideBySig”似乎不像我想的那样工作。它会一直返回“true”。似乎并不表示“new”关键字。MSDN http://msdn.microsoft.com/en-us/library/system.reflection.methodbase.ishidebysig.aspx声称它将在使用C#“new”修饰符声明的成员上返回true,但它也会对所有其他情况(如“override”,新声明的方法和extern)返回“true”! - CallMeLaNN
1
你说得对。显然它也没有做我想象中的那样 :P 在这种情况下,你的IsHiding方法有时可能会很有用(我应该编辑我的代码以修复与我这种误解相关的错误XD)。 - JBSnorro
现在IsHiding应该是完美的,并支持基类型中的方法重载。因此,现在我可以基于此扩展生成new关键字。 - CallMeLaNN

0
你可以尝试这个。
public static bool IsOverriding(this MethodInfo methodInfo)
{
    if (methodInfo == null) throw new ArgumentNullException();
    return methodInfo.GetBaseDefinition() != methodInfo;
}

那不起作用。考虑这种情况抽象类A { public abstract void Foo(); } 抽象类B:A {},然后typeof(B)。GetMethod(“Foo”)。IsOverriding()将返回true,但它不应该。 - JBSnorro

0

好的,我也不知道那会如何发挥作用。你的代码确实确定了一个方法是否被定义或重写。

在隐藏的情况下,声明类型通过new隐藏方法。 在泛型的情况下,所有的方法都由模板类定义。


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