在C#中,我如何在运行时创建一个值类型变量?

3

我正在尝试实现以下类似的方法:

(Func<T> getFn, Action<T> setFn) MakePair<T>(T initialVal) {
}

它将返回两个运行时生成的lambda表达式,使用Expression树创建代码来获取和设置动态创建的变量。

我目前的解决方案是动态创建一个类型为该元素的数组,并引用它:

(Func<T> getFn, Action<T> setFn) MakePair<T>(T initialVal) {
    var dynvar = Array.CreateInstance(typeof(T), 1);
    Expression<Func<Array>> f = () => dynvar;
    var dynref = Expression.Convert(f.Body, typeof(T).MakeArrayType());
    var e0 = Expression.Constant(0);
    var getBody = Expression.ArrayIndex(dynref, e0);
    var setParam = Expression.Parameter(typeof(T));
    var setBody = Expression.Assign(Expression.ArrayAccess(dynref, e0), setParam);
    
    var getFn = Expression.Lambda<Func<T>>(getBody).Compile();
    var setFn = Expression.Lambda<Action<T>>(setBody, setParam).Compile();

    return (getFn, setFn);
}

有没有比使用数组更好的方法来创建可能是值类型变量的运行时变量,以便进行读写?
有没有更好的方法来引用运行时创建的数组,而不是使用lambda创建(字段?)引用,以在ArrayIndex / ArrayAccess方法调用中使用?
过多的背景信息对于那些想知道的人来说,最终这是在尝试创建类似于Perl哈希表的lvalues自动验证的东西时出现的。
想象一下,您有一个包含重复元素的List,并且想要创建一个Dictionary,它允许您查找列表中每个唯一T的计数。 您可以使用几行代码进行计数(在这种情况下,T是int):
var countDict = new Dictionary<int, int>();
foreach (var n in testarray) {
    countDict.TryGetValue(n, out int c);
    countDict[n] = c + 1;
}

但我想用LINQ来做这件事,并且我想避免双重索引countDict(有趣的是,ConcurrentDictionaryAddOrUpdate可以实现此目的),因此我使用Aggregate:

var countDict = testarray.Aggregate(new Dictionary<int,int>(), (d, n) => { ++d[n]; return d; });

但是这种方法存在几个问题。首先,Dictionary 不会为缺失的值创建一个值,因此您需要一种新类型的 Dictionary ,它使用种子lambda自动创建缺失的值:

var countDict = testarray.Aggregate(new SeedDictionary<int, Ref<int>>(() => Ref.Of(() => 0)), (d, n) => { var r = d[n]; ++r.Value; return d; });

但是您仍然面临lvalue问题,因此您需要用Ref类替换普通的int计数器。不幸的是,C#无法创建一个类似于C++中的Ref类,但是使用基于自动生成setter lambda的getter lambda(使用表达式树)的类足够接近。 (不幸的是,尽管++d[n].Value;应该是有效的,但C#仍然不会接受,因此您必须创建一个临时变量。)
但现在您面临创建多个运行时整数变量以保存计数的问题。我扩展了Ref<>类,以采用返回常量(ConstantExpression)的lambda,并创建一个运行时变量并使用常量构建getter和setter作为初始值。

1
使用表达式树的特定原因是什么?简单的lambda表达式也可以捕获局部变量。但我不知道这可能有什么用处。 - Pretasoc
@DanielA.White 我添加了一些背景解释。 - NetMage
@Pretasoc,我添加了一些背景解释。 - NetMage
字典不会为缺失的值创建一个值,因此您需要一种新类型的字典来自动创建缺失的值。为什么不检查.ContainsKey并给出初始计数0呢? - Cheng Chen
4
假设您有一个包含重复元素的 List<T>,想要创建一个Dictionary<T,int>,允许您查找列表中每个唯一T的计数。我的想象是 mylist.GroupBy(x=>x).ToDictionary(g => g.Key, g => g.Count()),我们就完成了。我不理解为什么您需要使用表达式树等东西。请问您能否解释一下? - Eric Lippert
显示剩余5条评论
1个回答

2

我同意一些评论者的观点,认为表达式树似乎是不必要的,因此这里提供了一个简单的实现,可以在没有它们的情况下使用所示的API:

"最初的回答"

struct Box<T> {
    public T Value;
}

(Func<T> getFn, Action<T> setFn) MakePair<T>(T initialVal) {
    var box = new Box<T> { Value = initialVal };
    return (() => box.Value, v => box.Value = v);
}

作为对所提问题的答复(如何在没有lambda的情况下定义dynref),那么,以下对dynvar和dynref的修改有什么问题吗?"最初的回答"
var dynvar = new T[] { initialVal };
var dynref = Expression.Constant(dynvar);

虽然这并没有回答问题(Ref类可以用来捕获已经存在的lvalue(变量或数组成员),也可以从常量创建一个动态的lvalue),但也许可以用来简化常量情况的实现。 - NetMage
“Expression”树被使用是因为“Ref”类是泛型的,但如果它可以用来简化常量情况,那么它确实回答了这个问题。 - NetMage
我改变了想法 - 我使用“表达式”树的原因并不重要,问题在于如何使用“表达式”树动态创建对象(变量)而不使用“数组”。 - NetMage
我认为这并不是对我的最终问题的完整回答,但它足够接近满足我的目的。 - NetMage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接