Подтвердить что ты не робот

Может ли "this" быть пустым в виртуальных методах С#? Что происходит с остальными методами экземпляра?

Мне было любопытно, есть ли способ для this быть пустым в виртуальном методе на С#. Я предполагаю, что это невозможно. Я видел, что в существующем коде, во время обзора кода, и я хотел бы быть на 100% уверен, чтобы прокомментировать его удаление, но я хотел бы получить некоторое подтверждение и некоторый контекст из сообщества. Это случай, когда this != null в любом методе нестатического/экземпляра? В противном случае это было бы исключение с нулевым указателем? Я думал о методах расширения и такой или любой функции С#, которую я мог бы не знать с годами Java.

4b9b3361

Ответ 1

Это не стандартный С#, но, кроме ответов Lasse и Jon, немного IL-fiddling вы можете совершать не виртуальный вызов (для виртуальных или не виртуальных методов), передавая нуль this:

using System;
using System.Reflection.Emit;

class Test
{
    static void Main()
    {
        CallWithNullThis("Foo");
        CallWithNullThis("Bar");
    }

    static void CallWithNullThis(string methodName)
    {
        var mi = typeof(Test).GetMethod(methodName);

        // make Test the owner type to avoid VerificationException
        var dm = new DynamicMethod("$", typeof(void), Type.EmptyTypes, typeof(Test));
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldnull);
        il.Emit(OpCodes.Call, mi);
        il.Emit(OpCodes.Ret);

        var action = (Action)dm.CreateDelegate(typeof(Action));
        action();
    }

    public void Foo()
    {
        Console.WriteLine(this == null ? "Weird" : "Normal");
    }

    public virtual void Bar()
    {
        Console.WriteLine(this == null ? "Weird" : "Normal");
    }
}

Ответ 2

Это невозможно сделать в обычном С# (т.е. вызывать метод или свойство обычным образом), независимо от того, является ли метод виртуальным или нет.

Для не виртуальных методов вы можете создать делегат из открытого метода экземпляра, эффективно рассматривая метод экземпляра как статический метод с первым параметром с целевым типом. Вы можете вызвать этот делегат с нулевым аргументом и наблюдать this == null в методе.

Для виртуального метода вам нужно будет вызвать метод не виртуально - что может случиться с вызовом, например base.Foo(...)... но я не уверен, есть ли способ сделать этот вид не- -виртуальный вызов с нулевым аргументом. Делегатский подход определенно не работает здесь.

Демо-код:

using System;

class Test
{
    static void Main()
    {
        Action<Test> foo = (Action<Test>)
            Delegate.CreateDelegate(typeof(Action<Test>), typeof(Test).GetMethod("Foo"));
        foo(null); // Prints Weird
        Action<Test> bar = (Action<Test>)
            Delegate.CreateDelegate(typeof(Action<Test>), typeof(Test).GetMethod("Bar"));

        bar(null); // Throws
    }

    public void Foo()
    {
        Console.WriteLine(this == null ? "Weird" : "Normal");
    }

    public virtual void Bar()
    {
        Console.WriteLine(this == null ? "Weird" : "Normal");
    }
}

Ответ 3

Невозможно, чтобы this был нулевым в виртуальном вызове. Если у вас есть нулевая ссылка, у вас нет экземпляра объекта, и если у вас нет экземпляра объекта, тогда невозможно получить тип объекта для определения того, какой виртуальный метод вызывать.

В не виртуальном методе this также не может быть нулевым, но это потому, что компилятор не позволит вам совершить вызов, теоретически возможно сделать вызов. Вы можете вызвать метод расширения по ссылке null, хотя это сделает параметр this null.

Аналогичным образом теоретически можно сделать не виртуальный вызов виртуального метода, используя нулевую ссылку. Указав точный метод и используя отражение, возможно, обойти ограничения в компиляторе и вызвать метод с нулевой ссылкой.

Ответ 4

Невозможно непосредственно в коде С# (если вы не генерируете динамический код с помощью Reflection.Emit или подобных методов) , но, используя прямой код IL, можно вызвать виртуальный метод и иметь this == null.

Возьмите этот код:

using System;

public class C {
    public virtual void M() {
        Console.WriteLine("Inside the method M. this == null: {0}", this == null);
    }
}

public class Program {
    public static void Main(string[] pars)
    {
        C obj = null;
        obj.M();
    }
}

сохраните его до testnull.cs. В командной строке Visual Studio выполните следующие действия:

csc.exe testnull.cs

ildasm.exe testnull.exe/out:testnull.il

то посмотрите в testnull.il на эту строку кода:

callvirt   instance void C::M()

и измените его на:

call   instance void C::M()

и сохраните.

ilasm.exe testnull.il/out:testnull2.exe

попробуйте запустить его:

testnull2.exe

и вы получите:

Внутри метода M. this == null: True

если вы хотите, полный код il:

//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.33440
//  Copyright (c) Microsoft Corporation.  All rights reserved.



// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly testnull
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                             63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module testnull.exe
// MVID: {D8510E3B-5C38-40B9-A5A2-7DAE75DE1642}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x00300000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit C
       extends [mscorlib]System.Object
{
  .method public hidebysig newslot virtual 
          instance void  M() cil managed
  {
    // Code size       22 (0x16)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Inside the method M. this == null: {0}"
    IL_0006:  ldarg.0
    IL_0007:  ldnull
    IL_0008:  ceq
    IL_000a:  box        [mscorlib]System.Boolean
    IL_000f:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object)
    IL_0014:  nop
    IL_0015:  ret
  } // end of method C::M

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method C::.ctor

} // end of class C

.class public auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main(string[] pars) cil managed
  {
    .entrypoint
    // Code size       11 (0xb)
    .maxstack  1
    .locals init (class C V_0)
    IL_0000:  nop
    IL_0001:  ldnull
    IL_0002:  stloc.0
    IL_0003:  ldloc.0
    IL_0004:  call   instance void C::M()
    IL_0009:  nop
    IL_000a:  ret
  } // end of method Program::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Program::.ctor

} // end of class Program


// =============================================================

// *********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file testnull.res

Обратите внимание, что код, который должен быть записан в коде IL, является кодом вызова (код, который выполняет вызов), а не вызываемым кодом (метод virtual, который вызывается).

В исходных версиях компилятора С# (возможно, внутренних предварительных альфа-версий С# 1.0... изменение, которое я здесь говорил, было сделано в конце 1999 года, а С# 1.0 было выпущено в 2002 году), программисты Microsoft иногда пытались генерировать методы call вместо методов callvirt (callвызовы не выполняют null проверки, а вызовы callvirt делают это)), но после обнаружения того, что можно было сделать вызов метод экземпляра, имеющий this == null, они решили всегда использовать методы callvirt для примеров (см. здесь).

Ответ 5

На самом деле это странный вопрос. Виртуальный метод - это метод экземпляра, поэтому это объект объекта.

Вы не можете использовать виртуальный модификатор со статическими, абстрактными, частными или переопределяющими модификаторами.

MSDN: https://msdn.microsoft.com/en-us/library/9fkccyh4.aspx

IDE скажет вам, почему тоже

Ответ 6

Фактически возможно, что this имеет значение null в методе экземпляра.

Вот короткая LINQPad программа, которая демонстрирует:

void Main()
{
    var method = typeof(Test).GetMethod("Method");

    var d = new DynamicMethod("xx", typeof(void), new Type[0]);
    var il = d.GetILGenerator();
    il.Emit(OpCodes.Ldnull);
    il.Emit(OpCodes.Call, method);
    il.Emit(OpCodes.Ret);

    var a = (Action)d.CreateDelegate(typeof(Action));
    a();
}

public class Test
{
    public void Method()
    {
        this.Dump();
    }
}

Вывод:

null

В основном я делаю вызов непосредственно методу с нулевой ссылкой в ​​стеке. Я сомневаюсь, что компилятор С# действительно создаст такой код, но, поскольку это возможно, это может произойти.

Теперь что касается остальных:

  • Чтобы сделать виртуальный вызов, вам нужно пройти таблицу виртуальных методов, для этого вам нужен экземпляр, поэтому нет, this не может быть пустым в виртуальном методе
  • Метод расширения - это статический метод, а для статических методов не существует this.

Я также тестировал вышеуказанный код с помощью виртуального метода и получил это исключение при вызове a():

VerificationException
Операция может дестабилизировать время выполнения.