Как снять ограничение на ключевое слово ref при вызове вашего метода с параметрами ref? - PullRequest
0 голосов
/ 21 февраля 2019

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

Разработчики языка C # хотят, чтобы вызывающие абоненты знали, что они делают, поэтому регулярно вы не можетепросто удалите ключевые слова ref (или out) при вызове, например, от foo(ref bar) до foo(bar).Но что, если вы действительно хотите нарушить языковые ограничения, независимо от философии дизайна?

Цель:

var (a, b) = ((5, 6), (7, 8));
a.Swap(b);
a.Item1.Swap(a.Item2);
b.Item1.Swap(b.Item2);
Console.WriteLine($"a=({a.Item1},{a.Item2}),b=({b.Item1},{b.Item2})");

, которая выдает

a=(8,7),b=(6,5)

1 Ответ

0 голосов
/ 21 февраля 2019

Единственным решением для передачи аргумента по ссылке без дополнительного ключевого слова является Модификатор in для параметров в C # 7.2.Он не требует явного ключевого слова in при вызове, поскольку он предназначен для вызывающей стороны, чтобы не назначать какие-либо значения аргументу by-ref, чтобы вызывающим абонентам не нужно было уведомляться о том, что они делают.Но на самом деле in - это не более чем ref с атрибутом, который помечает его как предназначенный только для чтения, компилятор C # не допускает какого-либо присваивания.Но когда ссылка передается каким-либо иным способом, кроме присваивания (включая строго типизированную передачу), это ограничение может быть преодолено.

public class RefUnlocker
{
    private delegate ref T InToRef<T>(in T arg) where T : struct;

    private static ref T RefWrapper<T>(ref T arg) where T : struct => ref arg;

    private static readonly MethodInfo _refWrapper = typeof(RefUnlocker)
        .GetMethod(nameof(RefWrapper), BindingFlags.Static | BindingFlags.NonPublic);

    public static ref T Unlock<T>(in T arg) where T : struct
        => ref ((InToRef<T>)Delegate.CreateDelegate(typeof(InToRef<T>),
            _refWrapper.MakeGenericMethod(typeof(T))))(arg);
}

Метод RefUnlocker.Unlock помогает преобразовать параметр in в ref один путем динамического генерирования делегата из метода (поскольку параметры in также являются типами ByRef, которые совпадают с параметрами ref, поэтому сигнатура метода соответствует делегату), что означает, что вы можете просто написать метод Swap, например

public static void Swap<T>(ref this T op1, in T op2)
    where T : struct
{
    var temp = op1;
    op1 = op2;
    RefUnlocker.Unlock(op2) = temp;
}

(Обратите внимание, что параметр ref в качестве параметра this в методе расширения не нуждается в ключевом слове ref при вызове, поскольку он не передается как параметр)

Итогда вы можете позвонить без каких-либо дополнительных ключевых слов, таких как

var (a, b) = ((5, 6), (7, 8));
a.Swap(b);
a.Item1.Swap(a.Item2);
b.Item1.Swap(b.Item2);
Console.WriteLine($"a=({a.Item1},{a.Item2}),b=({b.Item1},{b.Item2})");
// Outputs: a=(8,7),b=(6,5)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...