Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Store a reference to a value type?

Tags:

c#

reference

I am writing a "Monitor" object to facilitate debugging of my app. This Monitor object can be accessed at run time from an IronPython interpreter. My question is, is it possible in C# to store a reference to a value type? Say I have the following class:

class Test
{
    public int a;
}

Can I somehow store a "pointer" to "a" in order to be able to check it's value anytime? Is it possible using safe and managed code?

Thanks.

like image 365
Alex Turpin Avatar asked Feb 13 '10 00:02

Alex Turpin


People also ask

Where do you store reference types?

Reference Type variables are stored in the heap while Value Type variables are stored in the stack. Value Type: A Value Type stores its contents in memory allocated on the stack.

Where value types are stored?

While value types are stored generally in the stack, reference types are stored in the managed heap. A value type derives from System. ValueType and contains the data inside its own memory allocation. In other words, variables or objects or value types have their own copy of the data.

What does a reference type variable store?

Variables of reference types store references to their data (objects), while variables of value types directly contain their data. With reference types, two variables can reference the same object; therefore, operations on one variable can affect the object referenced by the other variable.

Which data type is a reference type?

Examples of reference data types are class, Arrays, String, Interface, etc. Examples of primitive data types are int, float, double, Boolean, long, etc.


4 Answers

You cannot store a reference to a variable in a field or array. The CLR requires that a reference to a variable be in (1) a formal parameter, (2) a local, or (3) the return type of a method. C# supports (1) but not the other two.

(ASIDE: It is possible for C# to support the other two; in fact I have written a prototype compiler that does implement those features. It's pretty neat. (See http://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/ for details.) Of course one has to write an algorithm that verifies that no ref local could possibly be referring to a local that was on a now-destroyed stack frame, which gets a bit tricky, but its doable. Perhaps we will support this in a hypothetical future version of the language. (UPDATE: It was added to C# 7!))

However, you can make a variable have arbitrarily long lifetime, by putting it in a field or array. If what you need is a "reference" in the sense of "I need to store an alias to an arbitrary variable", then, no. But if what you need is a reference in the sense of "I need a magic token that lets me read and write a particular variable", then just use a delegate, or a pair of delegates.

sealed class Ref<T> 
{
    private Func<T> getter;
    private Action<T> setter;
    public Ref(Func<T> getter, Action<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }
    public T Value
    {
        get { return getter(); }
        set { setter(value); }
    }
}
...
Ref<string> M() 
{
    string x = "hello";
    Ref<string> rx = new Ref<string>(()=>x, v=>{x=v;});
    rx.Value = "goodbye";
    Console.WriteLine(x); // goodbye
    return rx;
}

The outer local variable x will stay alive at least until rx is reclaimed.

like image 129
Eric Lippert Avatar answered Sep 28 '22 11:09

Eric Lippert


No - you can't store a "pointer" to a value type directly in C#.

Typically, you'd hold a reference to the Test instance containing "a" - this gives you access to a (via testInstance.a).

like image 38
Reed Copsey Avatar answered Sep 28 '22 09:09

Reed Copsey


Here is a pattern I came up with that I find myself using quite a bit. Usually in the case of passing properties as parameters for use on any object of the parent type, but it works just as well for a single instance. (doesn't work for local scope value types tho)

public interface IValuePointer<T>
{
    T Value { get; set; }
}
public class ValuePointer<TParent, TType> : IValuePointer<TType>
{
    private readonly TParent _instance;
    private readonly Func<TParent, TType> _propertyExpression;
    private readonly PropertyInfo _propInfo;
    private readonly FieldInfo _fieldInfo;

    public ValuePointer(TParent instance, 
                        Expression<Func<TParent, TType>> propertyExpression)
    {
        _instance = instance;
        _propertyExpression = propertyExpression.Compile();
        _propInfo = ((MemberExpression)(propertyExpression).Body).Member as PropertyInfo;
        _fieldInfo = ((MemberExpression)(propertyExpression).Body).Member as FieldInfo;
    }

    public TType Value
    {
        get { return _propertyExpression.Invoke(_instance); }
        set
        {
            if (_fieldInfo != null)
            {
                _fieldInfo.SetValue(_instance, value);
                return;
            }
            _propInfo.SetValue(_instance, value, null);
        }
    }
}

This can then be used like so

class Test
{
    public int a;
}
void Main()
{
    Test testInstance = new Test();
    var pointer = new ValuePointer(testInstance,x=> x.a);
    testInstance.a = 5;
    int copyOfValue = pointer.Value;
    pointer.Value = 6;
}

Notice the interface with a more limited set of template arguments, this allows you to pass the pointer to something that has no knowledge of the parent type.

You could even implement another interface with no template arguments that calls .ToString on any value type (don't forget the null check first)

like image 44
Spencer Rose Avatar answered Sep 28 '22 09:09

Spencer Rose


You can create ref-return delegate. This is similar to Erik's solution, except instead of getter and setter it use single ref-returning delegate.

You can't use it with properties or local variables, but it returns true reference (not just copy).

public delegate ref T Ref<T>();

class Test
{
    public int a;
}

static Ref<int> M()
{
    Test t = new Test();
    t.a = 10;

    Ref<int> rx = () => ref t.a;
    rx() = 5;
    Console.WriteLine(t.a); // 5
    return rx;
}
like image 21
Ondrej Petrzilka Avatar answered Sep 28 '22 11:09

Ondrej Petrzilka