GetType() 会撒谎吗?

Posted

技术标签:

【中文标题】GetType() 会撒谎吗?【英文标题】:GetType() can lie? 【发布时间】:2013-05-23 04:02:55 【问题描述】:

基于几天前在 SO 中提出的以下问题:GetType() and polymorphism 并阅读了Eric Lippert's 的答案,我开始思考是否让GetType() 不是虚拟的真的可以确保一个对象不会对它的Type 撒谎.

具体来说,Eric 的回答如下:

框架设计者不会添加一个极其危险的功能,例如允许对象谎报其类型,只是为了使其与同一类型的其他三个方法保持一致。

现在的问题是:我能否制作一个 确实 谎报其类型的对象,而不会立即显而易见?我在这里可能大错特错,如果是这种情况,我很乐意澄清,但请考虑以下代码:

public interface IFoo

    Type GetType();

以及上述接口的以下两种实现:

public class BadFoo : IFoo

    Type IFoo.GetType()
    
        return typeof(int);
    


public class NiceFoo : IFoo


那么如果你运行下面这个简单的程序:

static void Main(string[] args)

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    Console.WriteLine("BadFoo says he's a '0'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '0'", niceFoo.GetType().ToString());
    Console.ReadLine();

果然badFoo输出了错误的Type

现在我不知道这是否有任何严重影响,基于 Eric 将这种行为描述为“非常危险的功能”,但这种模式会构成可信的威胁吗?

【问题讨论】:

有趣的标题和主题! IFoo.GetTypeobject.GetType 不是一回事,所以除了风格不佳之外,这里没有发生任何不好的事情。编辑:通常GetType 将在编译时未知的某些对象上调用,在大多数情况下object 而不是一些狡猾的接口。 :) 你的头衔先生,让我开心。 您只需介绍 成员,也称为 GetType,具有相同的签名。这与重要的GetType 方法无关。您还可以使用new 修饰符关键字创建一个隐藏相关GetType 方法的公共实例方法。注意,如果你有一个像static Type Test<T>(T t) return t.GetType(); 这样的通用方法(对T 没有约束),那么像Test<IFoo>(new BadFoo()) 这样的东西仍然会调用原来的GetType 方法。 @Jamiec - 问题“这种模式会构成可信的威胁吗?”不是修辞。 【参考方案1】:

好问题!在我看来,如果 GetType 在对象上是虚拟的,那么您只能真正误导其他开发人员,而事实并非如此。

您所做的类似于隐藏 GetType,如下所示:

public class BadFoo

    public new Type GetType()
    
        return typeof(int);
    

有了这个类(并使用the sample code from the MSDN for the GetType() method),你确实可以拥有:

int n1 = 12;
BadFoo foo = new BadFoo();

Console.WriteLine("n1 and n2 are the same type: 0",
                  Object.ReferenceEquals(n1.GetType(), foo.GetType())); 
// output: 
// n1 and n2 are the same type: True

所以,哎呀,你成功地撒了谎,对吧? 好吧,是也不是……考虑一下,将其用作漏洞利用意味着将您的 BadFoo 实例用作某处方法的参数,该方法可能需要object 或对象层次结构的通用基类型。像这样的:

public void CheckIfInt(object ob)

    if(ob.GetType() == typeof(int))
    
        Console.WriteLine("got an int! Initiate destruction of Universe!");
    
    else
    
        Console.WriteLine("not an int");
    

CheckIfInt(foo) 打印“不是 int”。

因此,基本上(回到您的示例),您实际上只能使用某人针对您的IFoo 接口编写的代码来利用您的“说谎类型”,这非常明确地表明它具有“自定义”@ 987654328@ 方法。

只有当 GetType() 在对象上是虚拟的时,您才能创建一个“谎言”类型,该类型可以与上面的 CheckIfInt 等方法一起使用,从而在其他人编写的库中造成严重破坏。

【讨论】:

是的,它与阴影完全相同。最后一段是真正表明确实没有威胁的地方。谢谢!【参考方案2】:

有两种方法可以确定类型:

    在不能重载的类型上使用typeof

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '0'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '0'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '0'", typeof(BadFoo));
    Console.WriteLine("NiceFoo really is a '0'", typeof(NiceFoo));
    Console.ReadLine();
    

    将实例转换为object 并调用GetType() 方法

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '0'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '0'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '0'", ((object)badFoo).GetType());
    Console.WriteLine("NiceFoo really is a '0'", ((object)niceFoo).GetType());
    Console.ReadLine();
    

【讨论】:

您将如何在只获取IFoo badFoo 作为参数的方法中使用typeof typeof 不能应用于类的实例,这是我们在这里需要做的。你唯一的选择是GetType() 您的两个样本正在做两件不同的事情。第二行没有回答所需的问题 - 他们明确地“获取类型BadFoo”,但他们没有“获取变量类型badFoo。” 是的,对不起。像往常一样,我没有仔细阅读这个问题。我更新了我的答案,指出了确定类型的两种不同方式。 这就是我要指出的。那么你的评论有什么意义呢? :)【参考方案3】:

不,你不能让 GetType 撒谎。您只是在介绍一种新方法。只有知道此方法的代码才会调用它。

例如,您不能让第三方或框架代码调用您的新 GetType 方法而不是真正的方法,因为该代码不知道您的方法存在,因此永远不会调用它。

但是,您可以用这样的声明混淆您自己的开发人员。任何使用您的声明编译并使用类型为 IFoo 的参数或变量或从该类型派生的任何类型的代码确实会使用您的新方法。但由于这只影响你自己的代码,它并没有真正施加“威胁”。

如果您确实想为类提供自定义类型描述,这应该使用Custom Type Descriptor 来完成,也许可以通过使用TypeDescriptionProviderAttribute 注释您的类。这在某些情况下很有用。

【讨论】:

+1 表示您的第二段明确指出第 3 方代码不了解自定义 GetType 实现。其他答案暗示了这个想法,但并没有真正说出来(至少没有那么清楚)。【参考方案4】:

嗯,实际上已经有一个类型可以位于GetType:任何可以为空的类型。

This code:

int? x = 0; int y = 0;
Console.WriteLine(x.GetType() == y.GetType());

输出True


其实,不是int?在撒谎,只是隐式转换为object,将int?变成了装箱的int。但是您不能int?int 告诉GetType()

【讨论】:

这是预期的(或至少众所周知的)行为。 this question 的答案非常清楚地解释了这个概念,以及它的原因。 @brichins:嗯,我同意它是已知的,但我不能同意它是众所周知的-已知的。无论如何,这是GetType() 产生一些奇怪结果的情况。实际上,我已经问过几位同事关于非阴影GetType() 是否可以返回与实际对象的运行时类型不同的东西,每个人的回答都是“不”。【参考方案5】:

我认为不会,因为每个调用 GetType 的库代码都会将变量声明为“Object”或通用类型“T”

以下代码:

    public static void Main(string[] args)
    
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintObjectType("BadFoo", badFoo);
        PrintObjectType("NiceFoo", niceFoo);
        PrintGenericType("BadFoo", badFoo);
        PrintGenericType("NiceFoo", niceFoo);
    

    public static void PrintObjectType(string actualName, object instance)
    
        Console.WriteLine("Object 0 says he's a '1'", actualName, instance.GetType());
    

    public static void PrintGenericType<T>(string actualName, T instance)
    
        Console.WriteLine("Generic Type 0 says he's a '1'", actualName, instance.GetType());
    

打印:

对象 BadFoo 说他是“TypeConcept.BadFoo”

Object NiceFoo 说他是“TypeConcept.NiceFoo”

通用类型 BadFoo 说他是“TypeConcept.BadFoo”

Generic Type NiceFoo 说他是 'TypeConcept.NiceFoo'

这种代码会导致不良场景的唯一情况是在您自己的代码中,您将参数类型声明为 IFoo

    public static void Main(string[] args)
    
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintIFoo("BadFoo", badFoo);
        PrintIFoo("NiceFoo", niceFoo);
    

    public static void PrintIFoo(string actualName, IFoo instance)
    
        Console.WriteLine("IFoo 0 says he's a '1'", actualName, instance.GetType());
    

IFoo BadFoo 说他是 'System.Int32'

IFoo NiceFoo 说他是“TypeConcept.NiceFoo”

【讨论】:

【参考方案6】:

据我所知,最糟糕的情况是误导碰巧使用中毒类的无辜程序员,例如:

Type type = myInstance.GetType();
string fullName = type.FullName;
string output;
if (fullName.Contains(".Web"))

    output = "this is webby";

else if (fullName.Contains(".Customer"))

    output = "this is customer related class";

else

    output = "unknown class";

如果myInstance 是您在问题中描述的类的实例,它将被视为未知类型。

所以我的回答是否定的,在这里看不到任何真正的威胁。

【讨论】:

当然。细心的程序员可以在编译时看到他调用了哪个方法“GetType”。 Object.GetType()SomeUserdefinedInterfaceClassOrStruct.GetType() 不同。只是,如果您使用 dynamic 类型,您永远无法知道绑定时会发生什么。所以在这种情况下你应该使用dynamic x = expression; ... Type t = ((object)x).GetType(); @Jeppe 公平积分!我认为它证明了单独的答案是合理的,我的答案更侧重于不会那么小心的“无辜”程序员。【参考方案7】:

如果您想安全应对这种黑客攻击,您有一些选择:

先转换为对象

您可以调用原始的GetType() 方法,首先将实例转换为object

 Console.WriteLine("BadFoo says he's a '0'", ((object)badFoo).GetType());

结果:

BadFoo says he's a 'ConsoleApplication.BadFoo'

使用模板方法

使用这个模板方法也会给你真正的类型:

static Type GetType<T>(T obj)

    return obj.GetType();


GetType(badFoo);

【讨论】:

【参考方案8】:

object.GetTypeIFoo.GetType 之间存在差异。 GetType 在编译时在未知对象上调用,而不是在接口上调用。在您的示例中,输出 badFoo.GetType 是预期的行为,因为您重载了该方法。唯一的问题是,其他程序员可能会对这种行为感到困惑。

但是如果你使用typeof()它会输出类型相同,并且你不能覆盖typeof()

程序员还可以在编译时看到他调用了哪个方法GetType

所以对于你的问题:这种模式不会构成可信的威胁,但它也不是最好的编码风格。

【讨论】:

badFoo.GetType() 是预期行为,因为 GetType 已超载。

以上是关于GetType() 会撒谎吗?的主要内容,如果未能解决你的问题,请参考以下文章

何时何地使用 GetType() 或 typeof()? [复制]

SQL bigint 数据类型被 gettype() 视为字符串;功能。为啥?

C# 反射 GetType() 异常

GetType obsolete

GetType、typeof 和 is,处理 null 和转换 [重复]

为啥我无法获取 GetType() 的 MethodBody?