【问题标题】:Get collection of class instance with property combinations获取具有属性组合的类实例的集合
【发布时间】:2012-11-27 16:20:46
【问题描述】:

我遇到了以下问题。

考虑以下简单属性。

[AttributeUsage(AttributeTargets.Property)]
public class CombinationsAttribute : Attribute
{
    public object[] PossibleValues { get; private set; }
    public CombinationsAttribute(params object[] values)
    {
        this.PossibleValues = values;
    }
}

以下是属性使用示例 - 只是一些具有一些虚拟属性的类,进入属性的值数组始终是属性类型。

public class MyClass
{

    [Combinations(1, 2, 3, 4, 5)]
    public int IntProperty1 { get; set; }

    [Combinations(10, 15, 20, 25, 30)]
    public int IntProperty2 { get; set; }

    [Combinations("X", "Y", "Z")]
    public string StringProperty { get; set; }
}

我想获取所有组合的所有实例(本例中为 5 * 5 * 3)。如何编写尽可能少的代码(LINQ 受青睐)?

编辑:我不知道类 (MyClass) - 有许多具有公共属性的类具有 CombinationsAttribute,我需要为其计算所有可能的组合。 . 这些类总是有无参数的构造函数。

预期结果示例(用于可视化的伪 c#):

List<MyClass> Combinations = GetCombinationMagicFunction(typeof(MyClass));

List[0] = MyClass { IntProperty1 = 1, IntProperty2 = 10, StringProperty = "X" }
List[1] = MyClass { IntProperty1 = 1, IntProperty2 = 10, StringProperty = "Y" }
List[2] = MyClass { IntProperty1 = 1, IntProperty2 = 10, StringProperty = "Z" }
List[2] = MyClass { IntProperty1 = 1, IntProperty2 = 15, StringProperty = "X" }
...
List[74] = MyClass { IntProperty1 = 5, IntProperty2 = 30, StringProperty = "Z" }

【问题讨论】:

  • “进入属性的值数组始终是属性类型”,但你有[Combinations("X", "Y", "Z")] public int StringProperty...

标签: c# linq reflection attributes combinatorics


【解决方案1】:

在 Eric Lippert 的CartesianProduct method 的帮助下:

private static IEnumerable<IEnumerable<T>> CartesianProduct<T>(IEnumerable<IEnumerable<T>> sequences) 
{ 
   IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() }; 
   return sequences.Aggregate(emptyProduct, (accumulator, sequence) => accumulator.SelectMany(accseq => sequence.Select(item => accseq.Concat(new[] { item }))));
}

public static IEnumerable<T> BuildCombinations<T>() where T : new()
{
   var query = from prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
               let attributes = prop.GetCustomAttributes(typeof(CombinationsAttribute), false)
               where attributes != null && attributes.Length != 0
               let attribute = (CombinationsAttribute)attributes[0]
               select attribute.PossibleValues.Select(value => new { prop, value })
   ;

   var combinations = CartesianProduct(query);
   foreach (var combination in combinations)
   {
      var item = new T();
      foreach (var pair in combination)
      {
         pair.prop.SetValue(item, pair.value, null);
      }

      yield return item;
   }
}

用法:

var list = BuildCombinations<MyClass>().ToList();

【讨论】:

  • 给定解决方案的工作和最简洁。谢谢:)
【解决方案2】:

我的基本出发点是沿着这些思路。您需要使用一些反射来重新设置值,但您应该明白这一点。

var properties = type.GetProperties()
                      .Where(prop => prop.IsDefined(typeof(CombinationsAttribute), false));
foreach(var prop in properties)
{
    allCombinations.Add(instance);
    var attributes = (CombinationsAttribute[])prop.GetCustomAttributes(typeof(CombinationsAttribute), false);
    foreach(var value in attributes)
    {
    }
}

【讨论】:

    【解决方案3】:
    private class PropertyItem
    {
        public PropertyInfo Property { get; set; }
        public List<object> PossibleValues { get; set; }
        public int CurrentIndex { get; set; }
        public int Count { get; set; }
    }
    
    public IEnumerable<T> GenerateCombinations<T>() where T : new()
    {
        return GenerateCombinations(typeof(T)).Cast<T>();
    }
    
    public IEnumerable<object> GenerateCombinations(Type type)
    {
        // Collect nessecery information
        var constructor = type.GetConstructor(Type.EmptyTypes);
        var properties =
            (
                from p in type.GetProperties()
                let values = p.GetCustomAttributes(typeof(CombinationsAttribute), true)
                    .Cast<CombinationsAttribute>()
                    .SelectMany(a => a.PossibleValues)
                    .ToList()
                where values.Count > 0
                select new PropertyItem
                {
                    Property = p,
                    PossibleValues = values,
                    CurrentIndex = 0,
                    Count = values.Count,
                }
            )
            .ToList();
    
        bool isDone;
        do
        {
            // Construct and return the current item
            var item = constructor.Invoke(new object[0]);
            foreach (var prop in properties)
            {
                prop.Property.SetValue(item, prop.PossibleValues[prop.CurrentIndex]);
            }
            yield return item;
    
            // Move the indices to the next item
            isDone = true;
            for (int i = 0; i < properties.Count; i++)
            {
                var prop = properties[i];
                if (prop.CurrentIndex == prop.Count - 1)
                {
                    prop.CurrentIndex = 0;
                }
                else
                {
                    prop.CurrentIndex++;;
                    isDone = false;
                    break;
                }
            }
        }
        while (!isDone);
    }
    

    用法:

    var allObjects1 = GenerateCombinations<MyClass>().ToList();
    var allObjects2 = GenerateCombinations(typeof(MyClass)).ToList();
    

    【讨论】:

    • 呵呵,我想这个问题没有简洁的解决方案,但我还是要试试,因为它仍然比我的解决方案短一倍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-13
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    • 2015-03-18
    相关资源
    最近更新 更多