简体   繁体   中英

In C#, how can I create a value type variable at runtime?

I am attempting to implement a method like:

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

It will return two runtime generated lambdas that get and set a dynamically created variable using Expression trees to create the code.

My current solution is to dynamically create an array of the type with one element, and reference that:

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

Is there a better way to create what may be a value type variable at runtime that can be read/written to than using an array?

Is there a better way to reference the runtime created array other than using a lambda to create the (field?) reference for use in the ArrayIndex / ArrayAccess method calls?

Excessive Background Info For those that wonder, ultimately this came up in an attempt to create something like Perl auto-virification of lvalues for Perl hashes.

Imagine you have a List<T> with duplicate elements and want to create a Dictionary<T,int> that allows you to look up the count for each unique T in the list. You can use a few lines of code to count (in this case T is int ):

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

But I want to do this with LINQ, and I want to avoid double-indexing countDict (interestingly, ConcurrentDictionary has AddOrUpdate for this purpose) so I use Aggregate:

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

But this has a couple of issues. First, Dictionary won't create a value for a missing value, so you need a new type of Dictionary that auto-creates missing values using eg a seed lambda:

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

But you still have the lvalue problem, so you replace the plain int counter with a Ref class. Unfortunately, C# can't create a C++ first class Ref class, but using one based around auto-creating a setter lambda from a getter lambda (using expression trees) is close enough. (Unfortunately C# still won't accept ++d[n].Value; even though it should be valid, so you have to create a temporary.)

But now you have the problem of creating multiple runtime integer variables to hold the counts. I extended the Ref<> class to take a lambda that returns a constant ( ConstantExpression ) and create a runtime variable and build a getter and setter with the constant being the initial value.

I agree with some of the question commenters that expression trees seem unnecessary, so here is a simple implementation of the shown API without them:

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

As an answer to the stated question (how to define dynref without a lambda), then, is there something wrong with the following modifications to dynvar and dynref ?

var dynvar = new T[] { initialVal };
var dynref = Expression.Constant(dynvar);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM