关于c#:GetType()可以骗?

GetType() can lie?

根据几天前在SO:GetType()和多态性中提出的以下问题并阅读Eric Lippert的答案,我开始考虑是否确保GetType()不是虚拟的,确保对象不能对其Type撒谎。

具体而言,Eric的答案陈述如下:

The framework designers are not going to add an incredibly dangerous feature such as allowing an object to lie about its type merely to make it consistent with three other methods on the same type.

现在的问题是:我可以创建一个对其类型有谎言的对象,而不是立即显而易见吗? 我可能在这里非常错误,如果是这种情况我想澄清,但请考虑以下代码:

1
2
3
4
public interface IFoo
{
    Type GetType();
}

以及所述接口的以下两个实现:

1
2
3
4
5
6
7
8
9
10
11
public class BadFoo : IFoo
{
    Type IFoo.GetType()
    {
        return typeof(int);
    }
}

public class NiceFoo : IFoo
{
}

然后,如果您运行以下简单程序:

1
2
3
4
5
6
7
8
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

现在我不知道这是否有任何严重影响,基于埃里克将这种行为描述为"难以置信的危险特征",但这种模式是否会构成可信的威胁?


好问题!我看待它的方式,如果GetType在对象上是虚拟的,那么你只能误导其他开发人员,事实并非如此。

你做的是类似于遮蔽GetType,如下所示:

1
2
3
4
5
6
7
public class BadFoo
{
    public new Type GetType()
    {
        return typeof(int);
    }
}

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

1
2
3
4
5
6
7
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或公共基类型。像这样的东西:

1
2
3
4
5
6
7
8
9
10
11
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之类的方法一起使用,以在其他人编写的库中创建破坏。


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

  • 在无法重载的类型上使用typeof

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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();

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

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

    但是,您可以将自己的开发人员与此类声明混淆。任何使用您的声明编译并使用类型为IFoo的参数或变量或从中派生的任何类型的代码确实会使用您的新方法。但由于这只会影响您自己的代码,因此并未真正强加"威胁"。

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


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

    这段代码:

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

    输出True

    实际上,不是int?是谎言,只是隐式转换为objectint?转换为盒装int。 但是你无法用intint告诉int?


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

    以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        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());
        }

    打印:

    Object BadFoo says he's a 'TypeConcept.BadFoo'

    Object NiceFoo says he's a 'TypeConcept.NiceFoo'

    Generic Type BadFoo says he's a 'TypeConcept.BadFoo'

    Generic Type NiceFoo says he's a 'TypeConcept.NiceFoo'

    这种代码导致错误情况的唯一时间是在您自己的代码中,您将参数类型声明为IFoo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        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 says he's a 'System.Int32'

    IFoo NiceFoo says he's a 'TypeConcept.NiceFoo'


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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是您在问题中描述的类的实例,则它将被视为未知类型。

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


    如果你想安全地对抗那种黑客,你有一些选择:

    先铸造对象

    您可以通过首先将实例强??制转换为object来调用原始GetType()方法:

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

    结果是:

    1
    BadFoo says he's a 'ConsoleApplication.BadFoo'

    使用模板方法

    使用此模板方法还将为您提供真实类型:

    1
    2
    3
    4
    5
    6
    static Type GetType< T >(T obj)
    {
        return obj.GetType();
    }

    GetType(badFoo);

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

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

    程序员也可以在编译时看到他调用的方法GetType

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