【问题标题】:How to get around lack of covariance with IReadOnlyDictionary?如何解决 IReadOnlyDictionary 缺乏协方差的问题?
【发布时间】:2012-11-15 15:28:18
【问题描述】:

我正在尝试公开一个只读字典,其中包含具有只读接口的对象。在内部,字典是可写的,其中的对象也是可写的(参见下面的示例代码)。我的问题是 IReadOnlyDictionary 不支持协变转换,因为问题here 中列出了原因。这意味着我不能将我的内部字典公开为只读字典。

所以我的问题是,有没有一种有效的方法可以将我的内部字典转换为 IReadOnlyDictionary,或者其他方式来处理这个问题?我能想到的选项是:

  1. 保存两个内部词典并保持同步。
  2. 在访问属性时创建一个新字典并转换其中的所有对象。
  3. 在内部使用时将 IReadOnly 转换回 NotReadOnly。

1 似乎很痛苦,2 似乎效率很低。 3 听起来是目前最有前途的,但仍然很难看。我还有其他选择吗?

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; }
}

【问题讨论】:

    标签: c# .net dictionary readonly covariance


    【解决方案1】:

    根据您的用例,您可能可以通过公开Func&lt;int,IReadOnly&gt; 来侥幸。

    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; }
    }
    

    【讨论】:

      【解决方案2】:

      另一种解决特定缺乏协方差的方法:

      一种特定类型的有用协方差的变通方法

      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;
              }   
          }
      

      【讨论】:

        【解决方案3】:

        我建议您可能要定义自己的协变接口,并包括协变访问方法以及创建只读包装器对象的方法,该对象使用所需类型实现 IDictionaryIReadonlyDictionary .只需在您的界面中忽略IEnumerable&lt;KeyValuePair&lt;TKey,TValue&gt;&gt;

        根据您正在做的事情,定义一个由IFetchByKey&lt;in TKey, out TValue&gt; 继承的IFetchByKey&lt;out TValue&gt; 可能会有所帮助,前者接受对任何类型对象的查询(给定一个对象实例,@987654326 的集合@ 应该能够说出它是否包含该实例,即使它是 DogToyotaPrius 类型;该集合不会包含后一种类型的任何实例,并且应该能够这样说)。

        【讨论】:

          【解决方案4】:

          也许这个解决方案适合你:

          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!";
              }
          }
          

          希望这会有所帮助!

          问候

          【讨论】:

          • 这适用于添加值,但我也在内部访问它们并对其进行更新。这就是我列出的选项#3 的立场,因为当我在内部检索它们时,我必须转换回 NotReadOnly。
          • 使用我提供的解决方案,您可以从类内部完全访问、更新、删除、添加等任何字典中的值,并且只将其作为只读字典向外公开。请举一个具体的例子,这个代码可能不够,这样我就可以更清楚地理解你的问题。
          • InternalDict[1].Name = "SomeNewValue" 没有演员表就无法工作。
          【解决方案5】:

          您可以为字典编写自己的只读包装器,例如:

          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();
              }
          }
          

          【讨论】:

          • +1 当我需要一个同时具有可变和不可变视图的字典时,我做了这样的事情。
          • 这更像是字典的只读视图。字典本身仍然可以更改。
          • @vidstige,“字典本身仍然可以改变”——当然,这就是它是私有的原因。
          • @vidstige 可以通过从构造函数中传入的字典创建新字典来轻松解决。
          • +1 因为您的类在创建时不会读取整个源字典。这意味着即使是庞大的词典,它也会很快。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-04-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-17
          • 1970-01-01
          相关资源
          最近更新 更多