Domanda

Qualcuno ha un modo più semplice per farlo? Sembra che dovrebbe essere più facile di così, ma sto avendo un blocco mentale. Fondamentalmente ho bisogno di rimuovere elementi da un dizionario e ricorrere ai valori degli elementi che sono anche dizionari.

private void RemoveNotPermittedItems(ActionDictionary menu)
{
    var keysToRemove = new List<string>();
    foreach (var item in menu)
    {
        if (!GetIsPermitted(item.Value.Call))
        {
            keysToRemove.Add(item.Key);
        }
        else if (item.Value is ActionDictionary)
        {
            RemoveNotPermittedItems((ActionDictionary)item.Value);
            if (((ActionDictionary)item.Value).Count == 0)
            {
                keysToRemove.Add(item.Key);
            }
        }
    }
    foreach (var key in (from item in menu where keysToRemove.Contains(item.Key) select item.Key).ToArray())
    {
        menu.Remove(key);
    }
}

Il dizionario delle azioni è così:

public class ActionDictionary : Dictionary<string, IActionItem>, IActionItem
È stato utile?

Soluzione

Non è necessario raccogliere le chiavi e ripeterle nuovamente se si itera il dizionario al contrario (da 'menu.Count - 1' a zero). Naturalmente, se si inizia a rimuovere le cose, l'iterazione in avanti produrrà eccezioni di raccolta mutate.

Non so cosa sia un ActionDictionary, quindi non ho potuto testare il tuo esatto scenario, ma ecco un esempio usando solo Dictionary<string,object>.

    static int counter = 0;
    private static void RemoveNotPermittedItems(Dictionary<string, object> menu)
    {
        for (int c = menu.Count - 1; c >= 0; c--)
        {
            var key = menu.Keys.ElementAt(c);
            var value = menu[key];
            if (value is Dictionary<string, object>)
            {
                RemoveNotPermittedItems((Dictionary<string, object>)value);
                if (((Dictionary<string, object>)value).Count == 0)
                {
                    menu.Remove(key);
                }
            }
            else if (!GetIsPermitted(value))
            {
                menu.Remove(key);
            }
        }
    }

    // This just added to actually cause some elements to be removed...
    private static bool GetIsPermitted(object value)
    {
        if (counter++ % 2 == 0)
            return false;
        return true;
    }

Ho anche invertito l'istruzione 'if', ma era solo un presupposto che avresti voluto fare il controllo del tipo prima di chiamare un metodo per agire sul valore dell'elemento ... funzionerà in entrambi i modi assumendo sempre 'GetIsPermitted' restituisce VERO per ActionDictionary.

Spero che questo aiuti.

Altri suggerimenti

Mentre foreach e GetEnumerator falliscono, un for-loop funziona,

var table = new Dictionary<string, int>() {{"first", 1}, {"second", 2}};
for (int i = 0; i < table.Keys.Count; i++)//string key in table.Keys)
{
    string key = table.Keys.ElementAt(i);
    if (key.StartsWith("f"))
    {
        table.Remove(key);
    }
}

Ma ElementAt () è una funzionalità di .NET 3.5.

Per cominciare, il tuo ciclo foreach è molto più complicato di quanto debba essere. Basta fare:

foreach (var key in keysToRemove)
{
    menu.Remove(key);
}

Sono un po 'sorpreso che Dizionario non abbia un metodo RemoveAll ma non sembra che ...

Opzione 1: un dizionario è ancora una raccolta. Scorri su menu.Values.

È possibile scorrere il menu. Valori e rimuoverli mentre si esegue l'iterazione. I valori non rientreranno in alcun ordine ordinato (che dovrebbe andare bene per il tuo caso). Potrebbe essere necessario utilizzare un ciclo for e regolare l'indice anziché utilizzare foreach: l'enumeratore genererà un'eccezione se si modifica la raccolta durante l'iterazione.

(Proverò ad aggiungere il codice quando sono sulla mia macchina di sviluppo Mon)

Opzione 2: crea un iteratore personalizzato.

Alcune raccolte restituite da ListBox SelectedItems in Winforms non contengono realmente la raccolta, ma forniscono un wrapper attorno alla raccolta sottostante. Un po 'come CollectionViewSource in WPF. ReadOnlyCollection fa anche qualcosa di simile.

Crea una classe che può " appiattire " i tuoi dizionari nidificati in qualcosa che può enumerarli come se fossero un'unica raccolta. Implementa una funzione di eliminazione che sembra rimuove un elemento dalla raccolta, ma rimuove davvero dal dizionario corrente.

A mio parere, puoi definire la tua classe generica derivante dal KeyValuePair < ... > sia TKey che TValue saranno List < T > e tu può utilizzare il RemoveAll o il RemoveRange dell'elenco < T > in un nuovo RemoveRange () o Metodo RemoveAll () nella classe derivata per rimuovere gli elementi desiderati.

So che probabilmente hai già trovato una buona soluzione, ma solo per il motivo di "scorrevolezza" se potessi modificare le firme del metodo in (so che potrebbe non essere appropriato nel tuo scenario):

private ActionDictionary RemoveNotPermittedItems(ActionDictionary menu)
{
 return new ActionDictionary(from item in menu where GetIsPermitted(item.Value.Call) select item)
.ToDictionary(d=>d.Key, d=>d.Value is ActionDictionary?RemoveNotPermittedItems(d.Value as ActionDictionary) : d.Value));
}

E posso vedere un paio di modi in cui puoi usare il tuo dizionario con elementi filtrati senza modifiche e materializzando nuovi dizionari.

Non è molto meno complicato, ma alcuni cambiamenti idiomatici lo rendono un po 'più breve e più facile per gli occhi:

    private static void RemoveNotPermittedItems(IDictionary<string, IActionItem> menu)
    {
        var keysToRemove = new List<string>();

        foreach (var item in menu)
        {
            if (GetIsPermitted(item.Value.Call))
            {
                var value = item.Value as ActionDictionary;

                if (value != null)
                {
                    RemoveNotPermittedItems(value);
                    if (!value.Any())
                    {
                        keysToRemove.Add(item.Key);
                    }
                }
            }
            else
            {
                keysToRemove.Add(item.Key);
            }
        }

        foreach (var key in keysToRemove)
        {
            menu.Remove(key);
        }
    }

    private static bool GetIsPermitted(object call)
    {
        return ...;
    }

Cambia il tipo di keysToRemove in HashSet < string > e otterrai un metodo O (1) Contiene . Con Elenca < string > è O (n), che è più lento come puoi immaginare.

non testato fino a quando non sarò sulla mia macchina VS domani: o

private void RemoveNotPermittedItems(ActionDictionary menu)
{
    foreach(var _checked in (from m in menu
                             select new
                             {
                                 gip = !GetIsPermitted(m.Value.Call),
                                 recur = m.Value is ActionDictionary,
                                 item = m
                             }).ToArray())
    {
        ActionDictionary tmp = _checked.item.Value as ActionDictionary;
        if (_checked.recur)
        {
            RemoveNotPermittedItems(tmp);
        }
        if (_checked.gip || (tmp != null && tmp.Count == 0) {
            menu.Remove(_checked.item.Key);
        }
    }
}

Penso

public class ActionSet : HashSet<IActionItem>, IActionItem

E

bool Clean(ActionSet nodes)
    {
        if (nodes != null)
        {
            var removed = nodes.Where(n => this.IsNullOrNotPermitted(n) || !this.IsNotSetOrNotEmpty(n) || !this.Clean(n as ActionSet));

            removed.ToList().ForEach(n => nodes.Remove(n));

            return nodes.Any();
        }

        return true;
    }

    bool IsNullOrNotPermitted(IActionItem node)
    {
        return node == null || *YourTest*(node.Call);
    }

    bool IsNotSetOrNotEmpty(IActionItem node)
    {
        var hset = node as ActionSet;
        return hset == null || hset.Any();
    }

Dovrebbe funzionare veloce

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top