Имея следующие два класса
interface IMask
{
string GetName();
}
readonly struct StructSource : IMask
{
string IMask.GetName()
{
throw new NotImplementedException();
}
}
Как мы можем передать ссылку на ValueType
, приведенную в интерфейс? Поэтому мне не нужно распространять общий аргумент в дальнейшем. Можно ли добиться этого без выделения кучи?
Предположим следующий идеальный мир.
using System.Runtime.CompilerServices;
void UseValue<T>(ref T value) where T : IMask
{
ref IMask ptr = ref Unsafe.As<T, IMask>(ref value);
UseInterfaceReference(in ptr);
}
void UseInterfaceReference(in IMask target)
{
target.GetName();
}
но это не работает должным образом, ссылка обнуляется, возможно, из-за несоответствия типов.
«Правильный» подход здесь — это то, что у вас уже есть в UseValue
, то есть ограниченный вызов (<T>
с where T : IWhatever
)
@ThomasWeller, поэтому правдоподобным решением было бы использовать ((IMask*)&value)
? Если он решает проблему, как предложено (без выделения кучи), это достаточно хорошо, единственная проблема, которую я вижу в этом, - это накладные расходы на добавление повсюду «unsafe» и «{}».
@ThomasWeller, попытка использовать ((IMask*)&value)
не сработала. У меня произошло то же самое, доступ к указателю через ->GetName()
throws nullref
Как мы можем передать ссылку на ValueType, приведенную к интерфейс? Поэтому мне не нужно распространять общий аргумент вниз по линия. Можно ли добиться этого без выделения кучи?
Я думаю, что вы имеете в виду:
Могу ли я просто использовать один универсальный метод (UseValue<T>
), который ограничен T:IMask
, в качестве точки входа, чтобы я мог в дальнейшем выполнять неупаковочные вызовы интерфейса IMask
в других неограниченных неуниверсальных методах?
Ответ - нет.
Вы можете понять, почему на самом деле, если бы у вас было две версии UseValue
, которые просто печатали результат (без ref
для упрощения).
void UseValueNonGeneric(IMask value){
var toPrint = value.GetName();
Console.WriteLine(toPrint);
}
void UseValue<T>(T value)
where T : IMask {
var toPrint = value.GetName();
Console.WriteLine(toPrint);
}
По сути, они делают одно и то же, но отличаются в одном — как компилятор генерирует код для фактического вызова интерфейса.
В нетиповом случае имеем
callvirt instance string IMask::GetName()
в общем случае имеем:
constrained. !!T
callvirt instance string IMask::GetName()
Во втором случае вы хотите избежать упаковки (распределения), как указано в документации об ограниченном префиксе:
Когда инструкция метода
callvirt
имеет префиксconstrained
thisType, инструкция выполняется следующим образом:
- ...
- Если thisType является типом значения, а thisType реализует метод, тогда ptr передается в неизмененном виде как указатель this на метод вызова.
- ...
Таким образом, независимо от того, удалось ли вам выполнить приведение или нет, вы потерпите неудачу, потому что для того, чтобы нераспределение (неупаковка) вообще могло произойти, вам нужен ограниченный общий метод.
И это просто не сработает:
void UseInterfaceReference(in IMask target)
{
target.GetName();
}
Почему бы не пропустить все это с помощью Unsafe? Другой метод требует IMask, у вас есть IMask, так что еще вам нужно? Просто передайте
value
методу