使用Expression <func <t>&gt; </func <t>使值类型表现为引用类型

时间:2010-09-06 19:21:44

标签: c# .net expression

我们知道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 个答案:

答案 0 :(得分:2)

您可以这样做:

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

当然这个解决方案并不能保证强大的编译时控制,因为编写器可以编写一个好的getter或setter。

可能,如果你需要类似指针的东西,你应该重新考虑你的设计,因为你可以用C#中的另一种方式来做:)

答案 1 :(得分:2)

以下是完整的解决方案:

// 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

要了解此解决方案的工作原理,您需要知道,只要在lambda表达式中使用变量,编译器就会相当大地转换代码(无论该lambda表达式是否成为委托或表达树)。它实际上生成一个包含字段的新类。删除变量 x 并替换为该字段。用法示例将如下所示:

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

代码检索的“字段”是包含 x 的字段,它检索的“常量”是 locals 实例。

答案 2 :(得分:0)

.NET中的整数是不可变的。我不确定你试图用这个问题解决什么问题。您是否考虑过创建一个具有“包装”整数的属性的类?该类将是一个引用类型,你想要实现的不需要任何“魔术”实用程序类 - 只是正常的C#引用类型行为。