Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created October 7, 2025 06:06
Show Gist options
  • Save sunmeat/e5de8fcae61e41b666abea064fdf0a84 to your computer and use it in GitHub Desktop.
Save sunmeat/e5de8fcae61e41b666abea064fdf0a84 to your computer and use it in GitHub Desktop.
vtable C#
using System.Text;
// https://pnguyen.au/posts/virtual-new-override-csharp/
// https://habr.com/ru/companies/clrium/articles/344556/ !!!
// https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/methodtable.h
class Dog
{
// в clr кожен об'єкт так само має прихований "вказівник" на таблицю в.методів (methodtable) на початку - як __vfptr у c++.
// ця таблиця статична для класу і містить адреси віртуальних методів (vtable слоти).
// якби це було явно в c#, могло б називатися __methodtableptr, аналог __vfptr.
public string? name;
public int age;
public Dog()
{
// clr автоматично ініціалізує поля на null/0
}
/* dog.ctor в il (.net 9, компілятор генерує такий байт-код):
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
ldarg.0 // завантажити this (об'єкт, що створюється) на стек - це посилання на новий об'єкт, як this у c++
call instance void [System.Private.CoreLib]System.Object::.ctor() // виклик базового конструктора object - clr ініціалізує об'єкт, встановлює methodtable на dog::methodtable
ldarg.0 // завантажити this знову
ldnull // завантажити null (порожній рядок)
stfld string Dog::name // зберегти null у поле name - поля ініціалізуються за замовчуванням
ldarg.0 // завантажити this
ldc.i4.0 // завантажити число 0
stfld int32 Dog::age // зберегти 0 у поле age
ret // повернути - об'єкт готовий, methodtable вже встановлена clr
}
// clr loader (завантажувач) при завантаженні класу заповнює dog::methodtable слотами: наприклад, слот для guard = адреса коду dog::guard.
*/
public virtual void Guard()
{
Console.WriteLine("Dog::Охороняти()");
}
public virtual void Bark()
{
Console.WriteLine("Dog::Гавкати()");
}
}
class Sheepdog : Dog
{
// похідний клас успадковує methodtable від dog, але clr оновлює слоти для перевизначених методів (override).
// наприклад, слот guard тепер вказує на sheepdog::guard, а не dog::guard.
public int sheepdog_field;
// явний конструктор sheepdog - викликає базовий, потім ініціалізує свої поля.
public Sheepdog() : base()
{
// логіка для похідного класу, якщо потрібно.
}
/* sheepdog.ctor в il (.net 9):
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
.maxstack 8
ldarg.0 // завантажити this
call instance void Dog::.ctor() // виклик базового dog.ctor - ініціалізує поля dog, встановлює methodtable на dog::methodtable тимчасово
ldarg.0 // завантажити this
ldc.i4.0 // завантажити 0
stfld int32 Sheepdog::sheepdog_field // ініціалізувати поле sheepdog_field
ldarg.0 // завантажити this востаннє
ldsflda valuetype [System.Private.CoreLib]System.Type Sheepdog::type_handle // завантажити адресу типу (але clr автоматично встановлює methodtable)
// насправді clr після виклику base перезаписує methodtable на sheepdog::methodtable з оновленими слотами для override
ret
}
// loader копіює pvtable від dog, але замінює слоти для перевизначених методів на sheepdog версії.
*/
public override void Guard()
{
Console.WriteLine("Sheepdog::Охороняти()");
}
public override void Bark()
{
Console.WriteLine("Sheepdog::Гавкати()");
}
}
class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
// створення sheepdog m в il (.net 9):
// newobj instance void Sheepdog::.ctor() // clr: виділити пам'ять за розміром sheepdog, викликати ctor (встановити methodtable на sheepdog::)
// stloc.0 // зберегти в змінну m
Sheepdog m = new Sheepdog();
// створення dog d:
// newobj instance void Dog::.ctor() // виділити, викликати ctor (встановити dog::methodtable)
Dog d = new Dog(); // clr: виділити пам'ять, встановити methodtable для dog
// виклик d.guard() в il (.net 9):
// ldloc.1 // завантажити d (this) на стек
// callvirt instance void Dog::Guard() // віртуальний виклик: clr шукає в methodtable слот guard і викликає відповідний метод
// callvirt - це динамічний диспетч, як (*(this->__vfptr[0]))() у c++, але через слоти в таблиці класу
d.Guard(); // callvirt instance void dog::guard() - il
// як clr виконує callvirt (приклад jit на x64, .net 9):
// this (rcx) перевіряється на null
// mov rax, [rcx] ; завантажити methodtable з offset 0 об'єкта (як __vfptr)
// mov rax, [rax + offset_слоту_guard] ; завантажити адресу методу з vtable (наприклад, offset 0x48 у methodtable)
// call rax ; виклик методу за адресою
// для non-virtual - прямий виклик, але virtual з override - динамічний пошук
// аналогічно для bark:
// ldloc.1
// callvirt instance void Dog::Bark()
d.Bark(); // аналогічно для слоту bark
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment