简体   繁体   English

C#:如何使用包含具体值的具体字典中的接口值的 IReadOnly 字典实现接口

[英]C#: How to implement an interface with an IReadOnly dictionary containing interface values from a concrete dictionary containing concrete values

In my code, I am declaring internal classes and public interfaces, and I have a situation where I want to expose an interface with an IReadonlyDictionary containing values of a an interface type, but I want to implement it with a class having a Dictionary with values of a concrete type.在我的代码中,我声明了内部类和公共接口,我有一种情况,我想公开一个带有 IReadonlyDictionary 的接口,其中包含一个接口类型的值,但我想用一个具有值的 Dictionary 的类来实现它具体类型。

I need this because I am deserializing some JSON into a concrete type, and I want to expose the deserialized data as readonly.我需要这个,因为我将一些 JSON 反序列化为具体类型,并且我想将反序列化的数据公开为只读。

I have implemented it as shown below, by lazily creating a new dictionary where concrete values are cast into the interface values.我已经实现了它,如下所示,通过懒惰地创建一个新字典,将具体值转换为接口值。 Is there a way to do this without having to create a new dictionary?有没有办法在不必创建新字典的情况下做到这一点?

    // In this code I want to expose an IReadonlyDictionary<string,ISomeValue>. 
    // The data is deserialized from JSON by using concrete type with a dictionary 
    // with values of type SomeValue which has to have public getter/setters to be deserialized.
    // I would like to do this without copying and casting into a new dictionary as shown here.
    // Is that possible?


    public interface ISomeValue { }
    internal class SomeValue : ISomeValue { }
    
    public interface IConfiguration {
        IReadOnlyDictionary<string, ISomeValue> Values{ get; }
    }
    
    internal class Configuration : IConfiguration  {
        public Configuration() {
            _values = new Lazy<IReadOnlyDictionary<string, ISomeValue>>(()
            => Values.ToDictionary(x=>x.Key,x=>(ISomeValue)x.Value));
        }
        public Dictionary<string, SomeValue> Values { get; } = null!;
        private Lazy<IReadOnlyDictionary<string, ISomeValue>> _values;
        IReadOnlyDictionary<string, ISomeValue> IConfiguration.Values=> _values.Value;
    }

I would like to do this without copying and casting into a new dictionary as shown here.我想这样做而不复制并转换到新字典中,如下所示。

Well, you can't just directly return Value in the explicit interface implementation, because both generic parameters of IReadOnlyDictionary are generically invariant .好吧,您不能在显式接口实现中直接返回Value ,因为IReadOnlyDictionary两个泛型参数都是泛型不变的 If only TValue were covariant, you would have been able to directly do this:如果只有TValue是协变的,你就可以直接这样做:

public Dictionary<string, SomeValue> Values { get; } = new Dictionary<string, SomeValue>();
IReadOnlyDictionary<string, ISomeValue> IConfiguration.Values => Values;

But TryGetValue uses TValue as a parameter, IReadOnlyDictionary is forced to be invariant on TValue .但是TryGetValue使用TValue作为参数, IReadOnlyDictionary被迫在TValue上保持不变。

I suggest that you make your code look prettier in some other aspects.我建议你让你的代码在其他一些方面看起来更漂亮。 For example, you could extract that very long line of code that creates a new dictionary into an extension method.例如,您可以将创建新字典的那行很长的代码提取到扩展方法中。

You have to create a new object, but you don't have to create a copy of the original dictionary.您必须创建一个新对象,但不必创建原始字典的副本。 Instead, you can create a view over the original dictionary, with the new shape.相反,您可以使用新形状在原始字典上创建视图。 Here's some entirely-untested-but-simple code that I believe should work:这是我认为应该可以工作的一些完全未经测试但简单的代码:

public class ReadOnlyDictionaryWrapper<TKey, TValue, TOriginalValue> : IReadOnlyDictionary<TKey, TValue>
    where TOriginalValue : TValue
{
    private readonly IReadOnlyDictionary<TKey, TOriginalValue> original;

    public ReadOnlyDictionaryWrapper(IReadOnlyDictionary<TKey, TOriginalValue> original) =>
        this.original = original;

    public TValue this[TKey key] => original[key];

    public IEnumerable<TKey> Keys => original.Keys;

    public IEnumerable<TValue> Values => original.Values.Cast<TValue>();

    public int Count => original.Count;

    public bool ContainsKey(TKey key) => original.ContainsKey(key);

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
        original.Select(pair => new KeyValuePair<TKey, TValue>(pair.Key, pair.Value))
                .GetEnumerator();

    public bool TryGetValue(TKey key, out TValue value)
    {
        bool ret = original.TryGetValue(key, out var originalValue);
        value = originalValue;
        return ret;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

I'd suggest creating a single instance of this in your Configuration constructor, to wrap _values .我建议在您的Configuration构造函数中创建一个这样的实例,以包装_values

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM