I\'m trying to expose a read-only dictionary that holds objects with a read-only interface. Internally, the dictionary is write-able, and so are the objects within (see belo
Another approach for a specific lack of covariance:
A work around for a specific type of useful covariance on 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;
}
}
You could write your own read-only wrapper for the dictionary, e.g.:
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();
}
}
Maybe this solutions works for you:
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!";
}
}
Hope this helps!
Greets
I would suggest that you might want to define your own covariant interfaces, and include covariant access methods as well as a method which will create a read-only wrapper object which implements either IDictionary
or IReadonlyDictionary
with the desired types. Simply ignore IEnumerable<KeyValuePair<TKey,TValue>>
within your interface.
Depending upon what you're doing, it may be helpful to define an IFetchByKey<out TValue>
which is inherited by IFetchByKey<in TKey, out TValue>
, with the former accepting queries for any type of object (given an object instance, a collection of Cat
should be able to say whether it contains that instance, even if it's a type Dog
or ToyotaPrius
; the collection won't contain any instances of the latter types, and should be able to say so).
Depending on your use case, you might be able to get away with exposing a 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; }
}