3
\$\begingroup\$

I have a class Feature as such:

public class Feature
{
    public string Name { get; set; }

    public string DisplayName { get; set; }

    public List<Feature> SubFeatures { get; set; }

    public bool Enabled { get; set; }

    public string[] DependsOn { get; set; }

    public bool IsRole { get; set; }

    public bool ManuallySelected { get; set; }
}

The DependsOn contains the Name of any other Features it depends on.

A Feature can only have a single parent (not seen directly at the Feature level, to find out what the parent of any given Feature is need to find out which Feature lists the child Feature as a SubFeature).

I then hold a List<Feature> containing all possible features in a var named myFeatures.

I then display these Feature to the user in a TreeListView (part of ObjectListView).

So far, so good. The problem is that I want to ensure that when a user selects a Feature, both the parent(s) of that Feature and the dependencies of that Feature (and parent(s) of the dependencies, and dependencies of the dependencies and parent(s) of the dependencies' dependencies ... etc) are also selected.

I also identify which Features a user selected manually (using ManuallySelected property) so that if they unselect any Feature at all, any parents and dependencies (and subdependencies and their parents etc) are also unselected, unless needed by any other Feature that was manually selected.

So this is my code for when the user click the CheckBox of a Feature...

treeListView1.CheckStatePutter = (rowObject, value) =>
{
    if (rowObject is Feature feature)
    {
        feature.Enabled = value == CheckState.Checked;
        feature.ManuallySelected = feature.Enabled;

        var flatFeatures = FlattenFeatures(_thisOperatingSystem.Features).ToList();

        foreach (var nonSelectedFeature in flatFeatures.Where(x => !x.ManuallySelected))
        {
            nonSelectedFeature.Enabled = false;
        }

        foreach (var selectedFeature in flatFeatures.Where(x => x.ManuallySelected))
        {
            foreach (var subFeature in GetFeatureDependencies(flatFeatures, selectedFeature))
            {
                subFeature.Enabled = true;
            }
        }

        treeListView1.Refresh();
    }

    return value;
};

And these are the helper functions that handle all recursion:

public IEnumerable<Feature> GetFeatureDependencies(List<Feature> flatFeatures, Feature feature)
{
    foreach (var parent in GetFeatureParents(flatFeatures, feature).Where(x => x != null))
    {
        yield return parent;

        foreach (var parentDependency in GetFeatureDependencies(flatFeatures, parent))
        {
            yield return parentDependency;
        }
    }

    foreach (var dependencyName in feature.DependsOn)
    {
        foreach (var dependency in flatFeatures.Where(x => x.Name == dependencyName))
        {
            yield return dependency;

            foreach (var parent in GetFeatureParents(flatFeatures, dependency).Where(x => x != null))
            {
                yield return parent;

                foreach (var parentDependency in GetFeatureDependencies(flatFeatures, parent))
                {
                    yield return parentDependency;
                }
            }

            foreach (var subDependency in GetFeatureDependencies(flatFeatures, dependency))
            {
                yield return subDependency;

                foreach (var parent in GetFeatureParents(flatFeatures, subDependency).Where(x => x != null))
                {
                    yield return parent;

                    foreach (var parentDependency in GetFeatureDependencies(flatFeatures, parent))
                    {
                        yield return parentDependency;
                    }
                }
            }
        }
    }
}

public IEnumerable<Feature> GetFeatureParents(List<Feature> flatFeatures, Feature feature)
{
    var parent = flatFeatures.FirstOrDefault(x => x.SubFeatures.Contains(feature));

    yield return parent;

    if (parent == null)
    {
        yield break;
    }

    foreach (var parentsParent in GetFeatureParents(flatFeatures, parent))
    {
        yield return parentsParent;
    }
}

public IEnumerable<Feature> FlattenFeatures(IEnumerable<Feature> features)
{
    foreach (var feature in features)
    {
        yield return feature;

        foreach (var child in FlattenFeatures(feature.SubFeatures))
        {
            yield return child;
        }
    }
}

The code works, but I think somewhere I made this far more complicated than it should be. Any help would be appreciated.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ The correct question title, which states your concern about the code, is too common to be useful. Our site standard is for the title to state what the code does — see How to Ask. \$\endgroup\$ Mar 8, 2017 at 14:54
  • \$\begingroup\$ @200_success I'd love for you to provide alternative titles, I think the title sucks too. That being said, I struggled to explain the issue in over 1000 characters, what makes you think I can do it in less than 1/10 of that? \$\endgroup\$
    – cogumel0
    Mar 8, 2017 at 15:03
  • 1
    \$\begingroup\$ Summarizing code is an important programming skill. Naming things succinctly and descriptively is hard! \$\endgroup\$ Mar 8, 2017 at 15:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.