Заставить тип значения вести себя как ссылочный тип, используя выражение - PullRequest
3 голосов
/ 06 сентября 2010

Мы знаем, что int является типом значения, поэтому имеет смысл следующее:

int x = 3;
int y = x;
y = 5;
Console.WriteLine(x); //says 3. 

Теперь немного кода, в котором мы хотим, чтобы из-за отсутствия лучшего термина «связать» две переменные указывали на одно и то же место в памяти.

int x = 3;
var y = MagicUtilClass.linkVariable(() => x);
y.Value = 5;
Console.WriteLine(x) //says 5.

Вопрос в следующем: как выглядит метод linkVariable? Как будет выглядеть тип возврата?

Несмотря на то, что я назвал сообщение как заставляющий тип значения вести себя как ссылочный тип, упомянутый метод linkVariable работает и для ссылочных типов .., т. Е.

Person x = new Person { Name = "Foo" };
var y = MagicUtilClass.linkVariable(() => x);
y.Value = new Person { Name = "Bar" };
Console.WriteLine(x.Name) //says Bar.

Я не уверен, как добиться этого в C # (кстати, нельзя использовать небезопасный код)?

Цените идеи. Спасибо.

Ответы [ 3 ]

2 голосов
/ 06 сентября 2010

Вы можете сделать что-то вроде этого:

public delegate void Setter<T>(T newValue);
public delegate T Getter<T>();
public class MagicPointer<T>
{
    private Getter<T> getter;
    private Setter<T> setter;

    public T Value
    {
        get
        {
            return getter();
        }
        set
        {
            setter(value);
        }
    }

    public MagicPointer(Getter<T> getter, Setter<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }

}

использование:

int foo = 3;
var pointer = new MagicPointer<int>(() => foo, x => foo = x);
pointer.Value++;
//now foo is 4

Конечно, это решение не гарантирует строгий контроль времени компиляции, потому что кодер должен написать хороший геттер или сеттер.

Вероятно, если вам нужно что-то вроде указателя, вам следует пересмотреть свой дизайн, потому что, скорее всего, вы можете сделать это по-другому в C #:)

2 голосов
/ 07 сентября 2010

Вот полное решение:

// Credits to digEmAll for the following code
public delegate void Setter<T>(T newValue);
public delegate T Getter<T>();
public class MagicPointer<T>
{
    private Getter<T> getter;
    private Setter<T> setter;

    public T Value
    {
        get { return getter(); }
        set { setter(value); }
    }

    public MagicPointer(Getter<T> getter, Setter<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }
}

// Code starting from here is mine
public static class MagicUtilClass
{
    public static MagicPointer<T> LinkVariable<T>(Expression<Func<T>> expression)
    {
        var memberExpr = expression.Body as MemberExpression;
        if (memberExpr == null)
            throw new InvalidOperationException("The body of the expression is expected to be a member-access expression.");
        var field = memberExpr.Member as FieldInfo;
        if (field == null)
            throw new InvalidOperationException("The body of the expression is expected to be a member-access expression that accesses a field.");
        var constant = memberExpr.Expression as ConstantExpression;
        if (constant == null)
            throw new InvalidOperationException("The body of the expression is expected to be a member-access expression that accesses a field on a constant expression.");
        return new MagicPointer<T>(() => (T) field.GetValue(constant.Value),
                                   x => field.SetValue(constant.Value, x));
    }
}

Использование:

int x = 47;
var magic = MagicUtilClass.LinkVariable(() => x);
magic.Value = 48;
Console.WriteLine(x);  // Outputs 48

Чтобы понять, почему это решение работает, вам нужно знать, что компилятор довольно сильно преобразует ваш код всякий раз, когда вы используете переменную внутри лямбда-выражения (независимо от того, становится ли это лямбда-выражение делегатом или выражением дерево). На самом деле он генерирует новый класс, содержащий поле. Переменная x удаляется и заменяется этим полем. Пример использования будет выглядеть примерно так:

CompilerGeneratedClass1 locals = new CompilerGeneratedClass1();
locals.x = 47;
var magic = MagicUtilClass.LinkVariable(() => locals.x);
// etc.

«Поле», которое извлекает код, является полем, содержащим x , а «константа», которую он извлекает, является экземпляром locals .

0 голосов
/ 06 сентября 2010

Целые числа в .NET являются неизменяемыми.Я не уверен, какую проблему вы пытаетесь решить с помощью этой проблемы.Рассматривали ли вы создание класса, у которого есть свойство, которое «оборачивает» целое число?Этот класс будет ссылочным типом, и то, что вы пытаетесь достичь, не потребует никаких «волшебных» служебных классов - просто нормальное поведение ссылочного типа C #.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...