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

时间:2019-01-09 23:55:12

标签: c# expression-trees

我正在尝试实现一种方法:

(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方法调用中使用的(field?)引用之外,还有一种更好的方法来引用运行时创建的数组吗?

过多的背景信息 对于那些想知道的人,最终是想创建类似Perl哈希值的Perl自动虚拟化的东西。

想象一下,您有一个包含重复元素的List<T>,并且想要创建一个Dictionary<T,int>,该列表允许您查找列表中每个唯一T的计数。您可以使用几行代码进行计数(在这种情况下,Tint):

var countDict = new Dictionary<int, int>();
foreach (var n in testarray) {
    countDict.TryGetValue(n, out int c);
    countDict[n] = c + 1;
}

但是我想用LINQ做到这一点,并且我想避免对countDict进行双索引(有趣的是,ConcurrentDictionary为此有AddOrUpdate),所以我使用了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; });

但是您仍然遇到左值问题,因此您用int类替换了普通的Ref计数器。不幸的是,C#无法创建C ++第一类Ref类,但是使用基于从getter lambda(使用表达式树)自动创建setter lambda(基于表达式树)的类已经足够接近。 (不幸的是,尽管C#应该是有效的,但它仍然不会接受++d[n].Value;,因此您必须创建一个临时目录。)

但是现在您遇到了创建多个运行时整数变量来保存计数的问题。我扩展了Ref<>类,以使用返回一个常量(ConstantExpression)的lambda并创建一个运行时变量,并以该常量为初始值来构建一个getter和setter。

1 个答案:

答案 0 :(得分:1)

我同意一些问题评论者的观点,即表达式树似乎是不必要的,因此这是所显示的API的简单实现,而没有它们:

var slider1 = noUiSlider.create($("#slider1")[0], {
  start: 0,
  behaviour: 'tap',
  connect: 'lower',
  step: 10,
  tooltips: false,
  range: {
      'min':  0,
      'max':  100
    }
});

var slider2 = noUiSlider.create($("#slider2")[0], {
  start: 0,
  behaviour: 'tap',
  connect: 'lower',
  step: 10,
  tooltips: false,
  range: {
    'min':  0,
    'max':  100
  }
});

作为对上述问题(如何在不使用lambda的情况下定义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); } 的答案)中,对dynrefdynvar进行以下修改是否有问题?

dynref