C#静态类和is运算符

15

最近我重构了一些代码,其中涉及到一些类的重命名,结果让我很惊讶的是我的一些代码以一种出乎意料的方式出现了错误。问题的原因是一个" is "运算符测试失败了,令我很惊讶的是这居然不是一个编译器错误或警告。

下面的完整程序展示了这种情况:

static class ExtensionMethods {}

class Program {

    static void Main() {
        Test("Test");
    }

    public static bool Test(object obj)
    {
        return obj is ExtensionMethods;
    }
}

考虑到ExtensionMethods是一个静态类,我本来希望"obj is ExtensionMethods"能够发出某种警告。

编译器将为“is”运算符发出警告,当测试对象永远不可能是提供的类型时,例如((string)obj) is System.Uri

我是否忘记了某些场景,使得这个测试实际上具有意义?


1
就此而言,ReSharper会捕捉到这个问题。 - Kirk Woll
考虑到传递到“Test”方法中的“object”实际上并未指定编译时类型,我不知道编译器如何区分。 - Robert Harvey
1
@Robert,这不总是错误的吗?因为没有任何东西可以成为静态类的实例。 - Kirk Woll
@Robert:当然可以,它的类型是System.Object。我希望编译器知道没有任何东西可以属于那种类型,因为那种类型是一个静态类,因此除非我忘记了什么,否则不可能有它的实例。 - MarkPflug
3
显然,编译器没有检查这个问题。而 System.Object 就像 O 型血一样;虽然很明显 String 不能是 System.Uri,但在运行时,Object 能否是 System.Uri 并不那么清晰。 - Robert Harvey
5个回答

11

我非常惊讶没有出现编译器错误或警告。

本应该有的,这是个疏忽。

涉及静态类的一些类似bug就像这样。如果我记得正确的话,甚至还有一些奇怪的情况被Vladimir Reshetnikov发现,其中可能会使类型推断将静态类型作为类型参数的限制。

显然,我之前见过这个问题,但这个问题从未得到解决。对于这个疏忽,请多多包涵。

我是否忘记了一个可以使这个测试实际上具有意义的场景?

没有。


4
根据C# 3.0规范第10.1.1.3节,静态类不能包含类基础规范(§10.1.4),也不能明确指定基类或实现的接口列表。因此,静态类隐式继承自类型对象。因此,编译器显然不会发出警告,因为它不知道“is”将始终返回false。(静态类是一个“object”,因此编译器在编译时不知道一个“object”是还是不是静态类。)实际上,它可能知道,或者至少可以找出来,但显然它没有专门针对这种情况进行检查。

1
我试着解决这个问题,虽然我在MSDN参考文献中找不到相关内容,但似乎is操作符取决于实例化一个类型以便能够检查。由于静态类不能被实例化(因为静态类是在编译时在程序堆栈上创建的对象)...
例如,如果您执行以下操作,则会收到以下错误:"Cannot declare a variable of static type"
ExtensionMethods ex;

如果您执行以下操作,则会收到以下错误:"无法创建静态类的实例"
ExtensionMethods ex2 = new ExtensionMethods();

为了演示这个问题,这里有一个完整的程序,展示了is运算符。
static class ExtensionMethods { }

// notice non-static
class AnotherNonStaticExtensionMethod { }

class Program
{
    static void Main(string[] args)
    {
        Debug.WriteLine(Test(new AnotherNonStaticExtensionMethod()).ToString());
        Debug.WriteLine(Test("Test").ToString());
        Debug.WriteLine(Test(4).ToString());
    }

    public static bool Test(object obj)
    {
        if (obj is ExtensionMethods)
        {
            return true;
        }
        else if (obj is AnotherNonStaticExtensionMethod)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

这是以下的输出:
True
False
False

这个对象能够通过第一个语句检查可实例化的类,这使我认为is运算符依赖于它。希望有人可以确认一下吗?
来自NominSim的提示: 从C# 3.0规范,第10.1.1.3节: 静态类不能包括类基础规范(§10.1.4),也不能明确指定基类或已实现接口列表。静态类隐式继承类型对象。

1

Eric Lippert于2013年的回答解释了这是Visual C# 5.0编译器(以及一些早期版本)的一个错误。这个问题也存在于as运算符中,例如object bad = obj as ExtensionMethods;

在C# 6.0(从2015年开始),您会获得一个编译时错误(而不仅仅是警告):

error CS7023: 'is' 或者 'as' 操作符的第二个操作数不可能是静态类型 'Xxxx'

然而,只有当您指定Strict特性时,才会出现这种情况,请参见另一个线程以获取详细信息


0
根据C#语言规范:

is运算符用于动态检查对象的运行时类型是否与给定类型兼容。操作E is T(E为表达式,T为类型)的结果是一个布尔值,表示E可以通过引用转换、装箱转换或拆箱转换成功地转换为类型T。

以及

静态类不能包含类基础说明(§10.1.4),也不能显式指定基类或实现接口列表。静态类隐含地继承自object类型。

由于它隐式继承自System.Object,因此编译器不发出警告是有道理的。

验证:

var staticBaseType = typeof(B).BaseType;

你会得到一个基本类型为 System.Object 的对象。


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