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