2017-04-20 22 views
13

Mam słownika:C# - Dodawanie obiektów, które implementują interfejsy do słownika

private Dictionary<Type, IExample> examples; 

Mam dwie klasy, które implementują interfejs:

public class Example1 : IExample 
{ 
} 

public class Example2 : IExample 
{ 
} 

Stworzyłem sposób, aby uzyskać instancję ze słownika, jeśli istnieje, ale próbuję wymyślić sposób utworzenia nowego obiektu, jeśli nie istnieje.

public T GetExample<T>() where T : IExample 
{ 
    // Return the signal if it exists 
    if (examples.ContainsKey(typeof(T))) 
    { 
     IExample value; 

     if (!examples.TryGetValue(typeof(T), out value)) 
     { 
      // unable to get value 
     } 

     return (T)value; 
    } 

    // Stuck on this line here. How exactly do I instantiate a new example if it doesn't exist. 
    examples.Add(typeof(T), new); 

    return default(T); 
} 

Czy to możliwe?

+4

marginesie: nie lubię „getter” metod, które mutują stanu. Metoda 'GetSomething()' powinna tylko * uzyskać * "coś", a nie dodawać czegoś do słownika. Lepszą nazwą dla twojej funkcji może być 'GetOrCreateExample ()' – Treb

Odpowiedz

13

Trzeba by dodać rodzajowego parametr typu presję na metody rodzajowe dla konstruktora parametr mniej, a następnie można utworzyć wystąpienia typu T parametr jak new T():

public T GetExample<T>() where T : IExample,class,new() 
{ 
     IExample value; 

     if (examples.TryGetValue(typeof(T), out value)) 
     { 
      return (T)value; 
     } 


    T obj = new T(); // create instance of T and use further down in code it's reference 
    examples.Add(typeof(T),obj); 

    return obj ; 
} 

i return default(T); wróci null nie nowa instancja T jak dla klasy (Typy referencyjne) domyślną wartością jest null, wątpię, czy chcesz wykonać tam return new T();, który utworzy nowy obiekt i zwróci referencję do niego z powrotem do osoby dzwoniącej.

+0

Kompilator narzeka na podwójną instrukcję where. "Klauzula ograniczenia została już określona" – user923

+0

Dziękujemy za aktualizację. – user923

+2

@ EhsanSajjad Chciałbym usunąć 'ContainsKey', ponieważ' TryGetValue' sam nie wystarcza do podniesienia wyjątków. –

1

Można użyć refleksji do instancji typu rodzajowego:

var newInstance = return Activator.CreateInstance<T>(); 

Jak zauważył Ehsan, trzeba dodać ograniczenie do rodzaju T więc może być instancja: where T: class.

Lub dodać new() ograniczenie where T: new(). Następnie można oznacz ją jako

var newInstance = new T(); 
+1

Dziękuję. Próbuję uniknąć refleksji, ale doceniam możliwość wyboru. – user923

1

Można użyć nowego ograniczenie @EhsanSajjad sugerowaną i stworzyć ogólną metodę rozszerzenia do słownika:

public static class DictionaryExtensions 
{ 
    public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key) where TValue : new() 
    { 
     var exists = dictionary.TryGetValue(key, out var value); 

     if (!exists) 
     { 
      value = new TValue(); 

      dictionary.Add(key, value); 
     } 

     return value; 
    } 
} 

out var będzie działać tylko jeśli używasz C# 7 , w przeciwnym razie musisz podzielić linię.

Albo jeszcze lepiej z opcjonalną wartość domyślną można przekazać:

public static class DictionaryExtensions 
{ 
    public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = null) where TValue : class, new() 
    { 
     var exists = dictionary.TryGetValue(key, out var value); 

     if (!exists) 
     { 
      value = defaultValue ?? new TValue(); 

      dictionary.Add(key, value); 
     } 

     return value; 
    } 
} 
+0

To nie dodaje nowo zainicjowanego obiektu do słownika, który powinien. – Maarten

+0

@Maarten źle odczytał wymagania, zmienię je ... gotowe. –

+0

Gdy "istnieje" jest prawdziwe, 'słownik.Add()' wyrzuca, ponieważ klucz już istnieje. Ponadto nowy obiekt wciąż nie jest dodawany do słownika. Co jeśli null jest oczekiwaną wartością domyślną? – piedar

1

Rozwijając na odpowiedź Ehsan jest, oto jak zrobiłbym to:

public T GetExample<T>() where T : IExample,class,new() 
{ 

    IExample value; 

    if (!examples.TryGetValue(typeof(T), out value)) 
    { 
     // Object doesn't exist. Create and add 
     value = new T(); 
     examples.Add(typeof(T), value); 
    } 

    return (T)value; 
} 

Korzystanie zarówno ContainsKey i TryGetValue jest zbędny.

8

Chociaż na pewno można utworzyć ograniczenie typu wymagające domyślnego konstruktora, często jest zbyt ograniczające: użytkownicy kodu mogą niechętnie udostępniać publiczny konstruktor domyślny lub w ogóle nie mieć konstruktora domyślnego.

Bardziej elastyczne podejście bierze delegata, który tworzy brakujący obiekt:

public T GetExample<T>(Func<T> make) where T : IExample, class { 
    IExample value; 
    if (examples.TryGetValue(typeof(T), out value)) { 
     return (T)value; 
    } 
    T res = make(); 
    examples.Add(typeof(T), res); 
    return res; 
} 

ten sposób rozmówca jest pod kontrolą, jak tworzyć nowe obiekty, gdy obiekt, że trzeba nie są buforowane w examples. Kod oddzwania „na żądanie”, więc logika tworzenie obiekt pozostaje mocno ukryte w kodzie rozmówcy, np

var e = exampleFactory.GetExample<MyExample>(
    () => new MyExample(firstParameter, secondParameter) 
); 
+0

Podoba mi się to podejście :) to dobry pomysł –

+0

byłoby dobrze, gdybyś dodał kod boczny dzwoniącego –

+0

@EhsanSajjad Dzięki, to dobry pomysł! Dodałem kod. – dasblinkenlight