GetType()可以说谎吗?


94

基于几天前在SO中问到的以下问题:GetType()和多态性,并阅读Eric Lippert的回答,我开始考虑是否将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将此行为描述为“ 令人难以置信的危险特征 ”而产生任何严重影响,但是这种模式是否会构成可信的威胁?


3
有趣的标题和主题!
大卫,

43
IFoo.GetType而且object.GetType不是同一件事,所以这里除了糟糕的风格之外,没有发生任何不好的事情。编辑:通常GetType将在编译时未知的某些对象上调用,object而不是在某些狡猾的接口上调用。:)
嬉皮士

4
您的头衔先生,我过得很愉快。
SonerGönül13年

5
您只需介绍具有相同签名的成员(也称为)GetType。这与GetType重要的方法无关。您还可以GetType使用new修饰符关键字制作一个隐藏相关方法的公共实例方法。请注意,如果您有一个类似的通用方法static Type Test<T>(T t) { return t.GetType(); }(对,没有任何限制T),那么诸如此类的东西Test<IFoo>(new BadFoo())仍将调用原始GetType方法。
杰普·斯蒂格·尼尔森

2
@Jamiec-问题“这种模式会构成可信的威胁吗?” 不修辞。
马丁·史密斯

Answers:


45

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

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

public class BadFoo
{
    public new Type GetType()
    {
        return typeof(int);
    }
}

使用此类(并使用MSDN的示例代码中的GetType()方法),您确实可以拥有:

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界面编写的代码来利用您的“说谎类型” ,这非常明确地表明了它具有“自定义” GetType()方法的事实。

仅当GetType()在对象上是虚拟的时,您才可以构建“说谎”类型,该类型可以与CheckIfInt上述方法一起使用,从而在其他人编写的库中造成破坏。


是的,它与阴影完全相同。最后一段真正使人很明显,实际上没有威胁。谢谢!
在它们之间隔

32

有两种确定类型的方法:

  1. typeof在不能重载的Type上使用

    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();
  2. 将实例转换为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();

1
如何typeof在仅获取IFoo badFooas参数的方法中使用?
huysentruitw

typeof不能应用于我们在这里需要做的类的实例。您唯一的选择是GetType()
在它们之间隔

您的两个样本正在做两种不同的事情。第二行没有回答所需的问题-它们明确地“获取类型BadFoo”,但没有“获取变量的类型” badFoo
Dan Puzey

是的,对不起。像往常一样,我没有足够仔细地阅读问题。我更新了答案,指出了确定类型的两种不同方式。
Johannes Wanzek

1
这就是我所指出的。那么您的评论有什么意义呢?:)
Johannes Wanzek

10

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

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

但是,您可以将自己的开发人员与此声明混淆。使用声明进行编译的任何代码,如果使用的参数或变量的类型为IFoo或派生自IFoo的任何类型,则实际上都将使用新方法。但是,由于那只会影响您自己的代码,因此并没有真正施加“威胁”。

如果确实要为类提供自定义类型描述,则应使用“ 自定义类型描述符”来完成,也许可以通过用TypeDescriptionProviderAttribute注释类来完成。在某些情况下这可能很有用。


2
第二段+1明确指出第三方代码不知道自定义GetType实现。其他答案暗示了这个想法,但并没有真正提出来(至少没有那么清楚)。
2013年

7

嗯,其实有已经能够处于一个类型GetType:任何可空类型。

此代码

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

输出True


其实,这不是int?谁在说谎,只是隐式转换为objectint?成盒装int。但无论如何,你不能告诉int?intGetType()


1
这是预期的(或至少是众所周知的)行为。这个问题的答案很清楚地解释了这个概念及其原因。
2013年

@brichins:好吧,我同意它是众所周知的,但是我不能同意它是众所周知的。无论如何,在这种情况下GetType()会产生一些奇怪的结果。我实际上已经问过几个同事,一个非阴影是否GetType()可以返回与实际对象的运行时类型不同的值,每个人的回答都是“否”。
Vlad13年

5

我认为不会,因为每个调用GetType的库代码都会将变量声明为“对象”或泛型“ 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”

对象NiceFoo说他是“ TypeConcept.NiceFoo”

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

通用类型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”


4

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

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是问题中描述的类的实例,则它将被视为未知类型。

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


1
当然。一个细心的程序员可以在编译时看到他调用了哪种方法“ GetType”。Object.GetType()有别于SomeUserdefinedInterfaceClassOrStruct.GetType()。只有使用这种dynamic类型,您才能永远不知道绑定时会发生什么。因此,您应该dynamic x = expression; ... Type t = ((object)x).GetType();在这种情况下使用。
杰普·斯蒂格·尼尔森

@Jeppe公平积分!我认为这证明了单独的答案是正确的,我的答案更多地关注不会那么小心的“无辜”程序员。
暗影巫师为您耳边

3

如果您想安全防范此类黑客攻击,可以采取一些措施:

首先投射到对象

您可以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);

2

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

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

程序员也可以在编译时看到哪种方法 GetType他调用了。

因此,您的问题是:这种模式不能构成可信的威胁,但它也不是最佳的编码风格。


badFoo.GetType()是预期的行为,因为GetType已超载。
huysentruitw
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.