C #: динамическое построение переменных - PullRequest
2 голосов
/ 16 января 2012

Я получаю от входа группу двойных переменных с именем: weight0, weight1 ... weight49.

Я хочу динамически вставить их в массив double для облегчения манипуляции,Но вместо того, чтобы называть каждый как: Weights[0] = weight0 ... Weights[49] = weight49 Я хочу сделать это с помощью одного цикла.

Есть ли способ сделать это?

Ответы [ 4 ]

4 голосов
/ 16 января 2012

Нет, в основном - если вы не имеете в виду в то же время, что вы создаете массив:

var weights = new[] {weight0, weight1, weight2, ... , weight48, weight49};

Лично мне бы хотелось избавиться от 50 переменных и использовать массив с самого начала, но это не всегда возможно во всех случаях.

1 голос
/ 16 января 2012

вы можете использовать отражение, чтобы определить индекс массива по именам переменных, но это далеко не эффективно.См. сообщение для деталей.

0 голосов
/ 16 января 2012

Я поднимаю это, потому что вы можете сделать это - , пока эти переменные на самом деле являются полями / свойствами . Нужно ли вам - это другое дело - это решение, которое можно многократно использовать, медленное (требует кэширования делегатов), и я должен сказать, что я согласен с Марком Грэйвеллом - рассмотрите возможность использования массива везде, если можете.

Если переменные являются свойствами, их нужно изменить. Также, если вам нужно записать массив обратно в переменные за один раз (поскольку это решение генерирует массив с копиями всех двойников, я бы не стал создавать массив объектов с двойными числами в штучной упаковке), для которого требуется другой метод ...

Так что вот так. Сначала священная стена кода / метод расширения:

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

Метод расширения, описанный выше, будет работать с любым типом. Он универсален как для типа экземпляра, так и для желаемого типа массива, чтобы упростить создание лямбда-кода в коде - хотя у него нет общего значения .

Вы передаете префикс имени для группы полей - в вашем случае "weight" - затем он ищет во всех открытых и закрытых полях экземпляра те из них с таким префиксом, которые также имеют суффикс, который можно проанализировать в целое число. Затем он упорядочивает эти поля на основе этого порядкового номера. Он не проверяет пропуски в порядковом списке - поэтому тип с weight0 и weight2 будет работать, но будет создавать только двухэлементный массив.

Затем он запекает динамический фрагмент кода с использованием деревьев выражений, компилирует его (в этот момент, как уже упоминалось в коде, было бы хорошо кэшировать делегат для TObj и TArray для будущего использования), а затем выполняет его, возвращая результат.

Теперь добавьте это к тестовому классу в стандартном модульном тестовом проекте:

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

Как я уже сказал - я не потворствую этому, и при этом я не жду никаких +1 за это - но я отстой в вызове:)

0 голосов
/ 16 января 2012

Я бы попытался сделать это с 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;

Я думаю, что это даст вам все параметры, которые могут вам понадобиться для массива.Попробуйте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...