Reputation: 161
I get from an input a group of double variables named: weight0
, weight1
...weight49
.
I want to dynamically insert them into a double
Array for easier manipulation.
But instead of calling each one like: Weights[0] = weight0
...Weights[49] = weight49
I want to do it with a single loop.
Is there a way to do it?
Upvotes: 2
Views: 269
Reputation: 42343
I'm putting this up because you can do it - so long as these variables are actually fields/properties. Whether you should is another matter - this solution, while reusable, is slow (needs delegate caching) and I have to say I agree with Marc Gravell - consider using an array throughout if you can.
If the variables are properties then it needs changing. Also if you need to write the array back to the variables in one shot (because this solution generates an array with copies of all the doubles, I wouldn't consider creating an object array with boxed doubles), that requires another method...
So here goes. First a holy wall of code/extension method:
//paste this as a direct child of a namespace (not a nested class)
public static class SO8877853Extensions
{
public static TArray[] FieldsToArray<TObj, TArray>(this TObj o,string fieldPrefix)
{
if(string.IsNullOrWhiteSpace(fieldPrefix))
throw new ArgumentException("fieldPrefix must not null/empty/whitespace",
"fieldPrefix");
//I've done this slightly more expanded than it really needs to be...
var fields = typeof(TObj).GetFields(System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic)
.Where(f =>f.Name.StartsWith(fieldPrefix) && f.FieldType.Equals(typeof(TArray)))
.Select(f =>new{ Field = f, OrdinalStr = f.Name.Substring(fieldPrefix.Length)})
.Where(f => { int unused; return int.TryParse(f.OrdinalStr, out unused);})
.Select(f => new { Field = f.Field, Ordinal = int.Parse(f.OrdinalStr) })
.OrderBy(f => f.Ordinal).ToArray();
//doesn't handle ordinal gaps e.g. 0,1,2,7
if(fields.Length == 0)
throw new ArgumentException(
string.Format("No fields found with the prefix {0}",
fieldPrefix),
"fieldPrefix");
//could instead bake the 'o' reference as a constant - but if
//you are caching the delegate, it makes it non-reusable.
ParameterExpression pThis = Expression.Parameter(o.GetType());
//generates a dynamic new double[] { var0, var1 ... } expression
var lambda = Expression.Lambda<Func<TObj, TArray[]>>(
Expression.NewArrayInit(typeof(TArray),
fields.Select(f => Expression.Field(pThis, f.Field))), pThis);
//you could cache this delegate here by typeof(TObj),
//fieldPrefix and typeof(TArray) in a Dictionary/ConcurrentDictionary
return lambda.Compile()(o);
}
}
The extension method above will work on any type. It's generic over both the instance type and desired array type to simplify the creation of the lambda in code - it doesn't have to be generic though.
You pass in the name prefix for a group of fields - in your case "weight"
- it then searches all the public and private instance fields for those with that prefix that also have a suffix which can be parsed into an integer. It then orders those fields based on that ordinal. It does not check for gaps in the ordinal list - so a type with weight0
and weight2
would work, but would only create a two-element array.
Then it bakes a dynamic piece of code using Expression trees, compiles it (at this point, as mentioned in the code, it would be good to cache the delegate against TObj
and TArray
for future use) and then executes it, returning the result.
Now add this to a test class in a standard unit test project:
private class SO8877853
{
private double field0 = 1.0;
private double field1 = -5.0;
private double field2 = 10.0;
public double[] AsArray()
{
//it would be nice not to have to pass both type names here - that
//can be achieved by making the extension method pass out the array
//via an 'out TArray[]' instead.
return this.FieldsToArray<SO8877853, double>("field");
}
}
[TestMethod]
public void TestThatItWorks()
{
var asArray = new SO8877853().AsArray();
Assert.IsTrue(new[] { 1.0, -5.0, 10.0 }.SequenceEqual(asArray));
}
Like I say - I'm not condoning doing this, nor am I expecting any +1s for it - but I'm a sucker for a challenge :)
Upvotes: 0
Reputation: 609
I would try to do it with a KeyValuePair- Listobject
// sample data
var weight = 1.00;
// create a list
var tmp = new List<KeyValuePair<string,object>>();
// Here you can add your variables
tmp.Add(new KeyValuePair<string,object>("weights" + tmp.Count.ToString()
, weight));
// If needed convert to array
var weights = tmp.ToArray();
// get the information out of the array
var weightValue = weights[0].Value;
var weightKey = weights[0].Key;
I think this will give you all the options, you might need for the array. Give it a try.
Upvotes: 0
Reputation: 326
you could use reflection to determine the index of the array from the variable names but this is far from efficient. See this post for details.
Upvotes: 1
Reputation: 1062800
No, basically - unless you mean at the same time that you create the array:
var weights = new[] {weight0, weight1, weight2, ... , weight48, weight49};
Personally, I'd be tempted to get rid of the 50 variables, and use the array from the outset, but that may not be possible in all cases.
Upvotes: 4