Making a value type behave as a reference type using Expression<Func<T>>
Asked Answered
H

3

3

We know that int is a value type and so the following makes sense:

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

Now, here is a bit of code where we want to for lack of a better term "link" the two variables point to the same memory location.

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

The question is: How does the method linkVariable look like? What would it's return type look like?

Although, I titled the post as making a value type behave as a reference type, the said linkVariable method works for reference types too.., i.e,

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

I am not sure how to achieve this in C# (not allowed to use unsafe code by the way)?

Appreciate ideas. Thanks.

Hebner answered 6/9, 2010 at 19:21 Comment(2)
Seems very much of a duplicate of this question.Ryurik
@Ryurik - Thanks for the link, but this is other way round. Sure, changing and value of a variable that is bound in a lambda, changes the result of evaluating the lambda. What I am interested in is for the value to be captured in this other thing, so that when the other thing is modified, the original changes to, i.e., when y is changed, x is changed.Hebner
S
2

Here is a full solution:

// 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));
    }
}

Usage:

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

To understand why this solution works, you need to know that the compiler transforms your code quite considerably whenever you use a variable inside a lambda expression (irrespective of whether that lambda expression becomes a delegate or an expression tree). It actually generates a new class containing a field. The variable x is removed and replaced with that field. The Usage example will then look something like this:

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

The “field” that the code retrieves is the field containing x, and the “constant” that it retrieves is the locals instance.

Saturation answered 6/9, 2010 at 19:21 Comment(0)
A
2

You can do something like this:

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;
    }

}

usage:

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

Of course this solution doesn't guarantee a strong compile time control, because is up to the coder to write a good getter or setter.

Probably, if you need something like a pointer you should reconsider your design, because likely you can do it in another way in C# :)

Athos answered 6/9, 2010 at 19:21 Comment(0)
M
0

Integers in .NET are immutable. I'm not sure what problem you're trying to solve with this issue. Have you considered creating a class which has a property which "wraps" the integer? That class would be a reference types and what you're trying to achieve would not require any "magic" utility classes - just normal C# reference-type behavior.

Meador answered 6/9, 2010 at 19:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.