2012-11-27 10 views
14

Próbuję odsłonić słownik tylko do odczytu, który przechowuje obiekty z interfejsem tylko do odczytu. Wewnętrznie słownik jest zdolny do zapisu, podobnie jak obiekty wewnątrz (zobacz poniżej przykładowy kod). Mój problem polega na tym, że IReadOnlyDictionary nie obsługuje konwersji kowariancyjnych z powodu przyczyny opisanej w pytaniu here. Oznacza to, że nie mogę po prostu odsłonić mojego wewnętrznego słownika jako tylko do odczytu.Jak obejść brak kowariancji za pomocą IReadOnlyDictionary?

Moje pytanie brzmi: czy istnieje skuteczny sposób na konwersję mojego wewnętrznego słownika na IReadOnlyDictionary lub w jakiś inny sposób, aby poradzić sobie z tym? Opcje, o których mogę pomyśleć to:

  1. Trzymaj dwa wewnętrzne słowniki i synchronizuj je.
  2. Utwórz nowy słownik, gdy właściwość jest dostępna i rzucić wszystkie obiekty w środku.
  3. Prześlij IReadOnly z powrotem do NotReadOnly, gdy używasz go wewnętrznie.

1 wydaje się być bólem, 2 wydaje się bardzo nieefektywne. 3 brzmi jak najbardziej obiecujący w tej chwili, ale wciąż jest brzydki. Czy mam jakieś inne opcje?

public class ExposesReadOnly 
{ 
    private Dictionary<int, NotReadOnly> InternalDict { get; set; } 
    public IReadOnlyDictionary<int, IReadOnly> PublicList 
    { 
     get 
     { 
      // This doesn't work... 
      return this.InternalDict; 
     } 
    } 

    // This class can be modified internally, but I don't want 
    // to expose this functionality. 
    private class NotReadOnly : IReadOnly 
    { 
     public string Name { get; set; } 
    } 
} 

public interface IReadOnly 
{ 
    string Name { get; } 
} 

Odpowiedz

11

Można napisać własną tylko do odczytu otoczka do słownika, np

public class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue> where TValue : TReadOnlyValue 
{ 
    private IDictionary<TKey, TValue> _dictionary; 

    public ReadOnlyDictionaryWrapper(IDictionary<TKey, TValue> dictionary) 
    { 
     if (dictionary == null) throw new ArgumentNullException("dictionary"); 
     _dictionary = dictionary; 
    } 
    public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); } 

    public IEnumerable<TKey> Keys { get { return _dictionary.Keys; } } 

    public bool TryGetValue(TKey key, out TReadOnlyValue value) 
    { 
     TValue v; 
     var result = _dictionary.TryGetValue(key, out v); 
     value = v; 
     return result; 
    } 

    public IEnumerable<TReadOnlyValue> Values { get { return _dictionary.Values.Cast<TReadOnlyValue>(); } } 

    public TReadOnlyValue this[TKey key] { get { return _dictionary[key]; } } 

    public int Count { get { return _dictionary.Count; } } 

    public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator() 
    { 
     return _dictionary 
        .Select(x => new KeyValuePair<TKey, TReadOnlyValue>(x.Key, x.Value)) 
        .GetEnumerator(); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 
+0

+1 Zrobiłem coś takiego, kiedy potrzebowałem słownika, który miał zarówno zmienne, jak i niezmienne widoki. –

+0

jest to bardziej jak widok tylko do odczytu słownika. Sam słownik wciąż może się zmienić. – vidstige

+0

@vidstige, "Sam słownik wciąż może się zmienić" - oczywiście, dlatego jest prywatny. – Joe

0

Może to rozwiązania działa dla Ciebie:

public class ExposesReadOnly 
{ 
    private IDictionary<int, IReadOnly> InternalDict { get; set; } 
    public IReadOnlyDictionary<int, IReadOnly> PublicList 
    { 
     get 
     { 
      IReadOnlyDictionary<int, IReadOnly> dictionary = new ReadOnlyDictionary<int, IReadOnly>(InternalDict); 

      return dictionary; 
     } 
    } 

    private class NotReadOnly : IReadOnly 
    { 
     public string Name { get; set; } 
    } 

    public void AddSomeValue() 
    { 
     InternalDict = new Dictionary<int, NotReadOnly>(); 
     InternalDict.Add(1, new NotReadOnly() { Name = "SomeValue" }); 
    } 
} 

public interface IReadOnly 
{ 
    string Name { get; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     ExposesReadOnly exposesReadOnly = new ExposesReadOnly(); 
     exposesReadOnly.AddSomeValue(); 

     Console.WriteLine(exposesReadOnly.PublicList[1].Name); 
     Console.ReadLine(); 

     exposesReadOnly.PublicList[1].Name = "This is not possible!"; 
    } 
} 

Nadzieja to pomaga!

Greets

+0

To służy do dodawania wartości, ale również uzyskuję do nich dostęp wewnętrznie i aktualizuję je. Tam właśnie stanąłem z opcją nr 3, którą wymieniłem, ponieważ musiałem odesłać do NotReadOnly, kiedy odzyskałem je wewnętrznie. – Ocelot20

+0

Dzięki oferowanemu rozwiązaniu możesz w pełni korzystać, aktualizować, usuwać, dodawać itp.dowolne wartości w słowniku od wewnątrz klasy i tylko wystawiać je na zewnątrz jako słownik tylko do odczytu. Podaj konkretny przykład, w którym ten kod może być niewystarczający, aby lepiej zrozumieć Twój problem. –

+0

'InternalDict [1] .Name =" SomeNewValue "' nie zadziała bez rzutowania. – Ocelot20

1

Sugerowałbym, że możesz zdefiniować własne interfejsy kowariantna i zawierać kowariantna metody dostępu a także metodę, która utworzy obiekt typu wrapper tylko do odczytu, który implementuje albo IDictionary lub IReadonlyDictionary z żądanymi typami. Po prostu zignoruj ​​IEnumerable<KeyValuePair<TKey,TValue>> w swoim interfejsie.

W zależności od tego, co robisz, może to być pomocne w celu określenia IFetchByKey<out TValue> który jest dziedziczony przez IFetchByKey<in TKey, out TValue> z dawnych przyjmowania zapytań dla każdego typu obiektu (biorąc pod uwagę wystąpienie obiektu, zbiór Cat powinien móc powiedzieć, czy zawiera ono tę instancję, nawet jeśli jest to typ Dog lub ToyotaPrius, kolekcja nie będzie zawierała żadnych wystąpień tych ostatnich typów i powinna być w stanie to powiedzieć).

0

Innym podejściem do konkretnego braku kowariancji:

Dzieło dokoła dla konkretnego typu użytecznego kowariancji na IDictionary

public static class DictionaryExtensions 
    { 
     public static IReadOnlyDictionary<TKey, IEnumerable<TValue>> ToReadOnlyDictionary<TKey, TValue>(
      this IDictionary<TKey, List<TValue>> toWrap) 
     { 
      var intermediate = toWrap.ToDictionary(a => a.Key, a =>a.Value!=null? a.Value.ToArray().AsEnumerable():null); 
      var wrapper = new ReadOnlyDictionary<TKey, IEnumerable<TValue>>(intermediate); 
      return wrapper; 
     } 
    } 
0

zależności od przypadku użycia, użytkownik może być w stanie uciec z odsłaniając Func<int,IReadOnly>.

public class ExposesReadOnly 
{ 
    private Dictionary<int, NotReadOnly> InternalDict { get; set; } 
    public Func<int,IReadOnly> PublicDictionaryAccess 
    { 
     get 
     { 
      return (x)=>this.InternalDict[x]; 
     } 
    } 

    // This class can be modified internally, but I don't want 
    // to expose this functionality. 
    private class NotReadOnly : IReadOnly 
    { 
     public string Name { get; set; } 
    } 
} 

public interface IReadOnly 
{ 
    string Name { get; } 
} 
Powiązane problemy