Skip to content

Commit

Permalink
Rejigger collection interfaces: ISource<T> returns
Browse files Browse the repository at this point in the history
- `ISource<T>` is the same as `IReadOnlyCollection<T>` but also
  implementing `ICount` and `IIsEmpty`.
- Added `IDictionarySource<K,V>` which is `IReadOnlyDictionary<K,V>`
  plus minor interfaces `IIndexed<K, V>`, `ITryGet<K, V>`, and
  `ISource<KeyValuePair<K, V>>`.
- Added `ISource<T>` as a new base interface of `IListSource<T>`.
  Because of this, all implementations of `IListSource<T>` now require
  an `IsEmpty` property.
- Added `IDictionarySource<K,V>` as a new base interface of
  `IDictionaryImpl<K,V>`. Because of this, all implementations of this
  interface and `IDictionaryEx<K,V>` must now provide an `IsEmpty`
  property and a `TryGet` method in addition to `TryGetValue`.
  `TryGet` is better than `TryGetValue` because it supports variance,
  e.g. `ITryGet<object, Symbol>` can be assigned to a variable of type
  `ITryGet<string, object>`.
-  Added `IsEmpty` and `TryGet` members to existing classes as required.
  • Loading branch information
qwertie committed Dec 23, 2020
1 parent 1dfc26e commit 56a6a00
Show file tree
Hide file tree
Showing 21 changed files with 152 additions and 116 deletions.
6 changes: 2 additions & 4 deletions Core/Loyc.Collections/ALists/AListBase.cs
Expand Up @@ -1227,10 +1227,8 @@ public ReverseBinumerator<T> GetEnumerator()
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() { return GetEnumerator(); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
public int Count
{
get { return _list.Count; }
}
public int Count => _list.Count;
public bool IsEmpty => _list.Count == 0;

IRange<T> IListSource<T>.Slice(int start, int count) { return Slice(start, count); }
public Slice_<T> Slice(int start, int count) { return new Slice_<T>(this, start, count); }
Expand Down
12 changes: 8 additions & 4 deletions Core/Loyc.Collections/Other/Bijection.cs
Expand Up @@ -144,6 +144,12 @@ public bool TryGetValue(K1 key, out K2 value)
{
return _map.TryGetValue(key, out value);
}
public K2 TryGet(K1 key, out bool fail)
{
K2 value = default;
fail = key == null || !TryGetValue(key, out value);
return value;
}

public K2 this[K1 key]
{
Expand Down Expand Up @@ -187,10 +193,8 @@ public void CopyTo(KeyValuePair<K1, K2>[] array, int arrayIndex)
_map.CopyTo(array, arrayIndex);
}

public int Count
{
get { return _map.Count; }
}
public int Count => _map.Count;
public bool IsEmpty => _map.Count == 0;

public bool IsReadOnly
{
Expand Down
6 changes: 0 additions & 6 deletions Core/Loyc.Collections/VLists/FWList.cs
Expand Up @@ -229,12 +229,6 @@ public T First
return Block.Front(LocalCount);
}
}
public bool IsEmpty
{
get {
return Count == 0;
}
}
/// <summary>Removes the front item (at index 0) from the list and returns it.</summary>
public T Pop()
{
Expand Down
6 changes: 0 additions & 6 deletions Core/Loyc.Collections/VLists/WList.cs
Expand Up @@ -231,12 +231,6 @@ public T Last
SetAtDff(0, value);
}
}
public bool IsEmpty
{
get {
return Count == 0;
}
}
/// <summary>Removes the back item (at index Count-1) from the list and returns it.</summary>
public T Pop()
{
Expand Down
1 change: 1 addition & 0 deletions Core/Loyc.Collections/VLists/WListBase.cs
Expand Up @@ -370,6 +370,7 @@ protected internal WListBase(VListBlock<T> block, int localCount, bool isOwner)
public new int IndexOf(T item) { return base.IndexOf(item); }
public new bool Contains(T item) { return base.Contains(item); }
public new void CopyTo(T[] array, int arrayIndex) { base.CopyTo(array, arrayIndex); }
public bool IsEmpty => base.Count == 0;
public new int Count { get { return base.Count; } }
public bool IsReadOnly { get { return false; } }
public new bool Remove(T item) { return base.Remove(item); }
Expand Down
7 changes: 3 additions & 4 deletions Core/Loyc.Essentials/Collections/Adapters/ListAsListSource.cs
Expand Up @@ -44,10 +44,9 @@ public sealed class ListAsListSource<T> : WrapperBase<IList<T>>, IListAndListSou
{
public ListAsListSource(IList<T> obj) : base(obj) { }

public int Count
{
get { return _obj.Count; }
}
public bool IsEmpty => _obj.Count == 0;
public int Count => _obj.Count;

public bool Contains(T item)
{
return _obj.Contains(item);
Expand Down
10 changes: 5 additions & 5 deletions Core/Loyc.Essentials/Collections/Adapters/ListSourceAsList.cs
@@ -1,4 +1,4 @@
/*
/*
* Created by SharpDevelop.
* User: Pook
* Date: 4/10/2011
Expand Down Expand Up @@ -64,10 +64,9 @@ public void Clear()
{
throw new NotSupportedException("Collection is read-only.");
}
public int Count
{
get { return _obj.Count; }
}

public int Count => _obj.Count;

public bool Contains(T item)
{
return _obj.Contains(item);
Expand Down Expand Up @@ -95,6 +94,7 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

#endregion

public bool IsEmpty => _obj.Count == 0;
public T TryGet(int index, out bool fail)
{
return _obj.TryGet(index, out fail);
Expand Down
Expand Up @@ -35,6 +35,7 @@ public sealed class ReadOnlyListAsListSource<T> : WrapperBase<IReadOnlyList<T>>,
public ReadOnlyListAsListSource(IReadOnlyList<T> obj) : base(obj) { }

public int Count => _obj.Count;
public bool IsEmpty => Count == 0;
public bool Contains(T item) => _obj.Contains(item);

public T this[int index] => _obj[index];
Expand Down
Expand Up @@ -25,6 +25,7 @@ public class CollectionWrapper<T, TCollection> : WrapperBase<TCollection>, IColl
public virtual void Clear() => _obj.Clear();
public virtual bool Remove(T item) => _obj.Remove(item);

public bool IsEmpty => Count == 0;
public virtual int Count => _obj.Count;
public virtual bool IsReadOnly => _obj.IsReadOnly;
public virtual bool Contains(T item) => _obj.Contains(item);
Expand Down
10 changes: 10 additions & 0 deletions Core/Loyc.Essentials/Collections/BaseClasses/DictionaryWrapper.cs
Expand Up @@ -32,6 +32,16 @@ public abstract class DictionaryWrapper<K, V, TDictionary> : CollectionWrapper<K

public virtual bool ContainsKey(K key) => _obj.ContainsKey(key);
public virtual bool TryGetValue(K key, out V value) => _obj.TryGetValue(key, out value);
public V TryGet(K key, out bool fail)
{
if (key != null) {
fail = !TryGetValue(key, out V value);
return value;
} else {
fail = true;
return default(V);
}
}

public virtual ICollection<K> Keys => _obj.Keys;
public virtual ICollection<V> Values => _obj.Values;
Expand Down
7 changes: 4 additions & 3 deletions Core/Loyc.Interfaces/Collections/Disambiguation interfaces.cs
Expand Up @@ -99,18 +99,19 @@ public interface IListImpl<T> : IList<T>, IListSource<T>, IListSink<T>, IListAnd
/// of <see cref="IListImpl{T}"/>.) Variables should not have this type (except in
/// disambiguation methods, which immediately cast the variable to another type).
/// </remarks>
public interface IDictionaryAndReadOnly<K, V> : IDictionary<K, V>, IReadOnlyDictionary<K, V>, IIndexed<K, V> { }
public interface IDictionaryAndReadOnly<K, V> : IDictionary<K, V>, IReadOnlyDictionary<K, V>, IIndexed<K, V>, ICollectionAndReadOnly<KeyValuePair<K, V>> { }

/// <summary>This interface is intended to be implemented by all Loyc collections
/// that implement <see cref="IDictionary{K,V}"/>. It combines the original
/// <see cref="IDictionary{K,V}"/> interface with its component interfaces
/// <see cref="IReadOnlyDictionary{K,V}"/> and <see cref="IDictionarySink{K,V}"/>.</summary>
/// <see cref="IReadOnlyDictionary{K,V}"/> and <see cref="IDictionarySink{K,V}"/>,
/// as well as the trivial interface <see cref="IMIndexed{K,V}"/>.</summary>
/// <remarks>
/// This interface is used in C# for disambiguation (as explained in the description
/// of <see cref="IListImpl{T}"/>.) Variables should not have this type (except in
/// disambiguation methods, which immediately cast the variable to another type).
/// </remarks>
public interface IDictionaryImpl<K, V> : IDictionary<K, V>, IDictionaryAndReadOnly<K, V>, IDictionarySink<K, V> { }
public interface IDictionaryImpl<K, V> : IDictionary<K, V>, IDictionaryAndReadOnly<K, V>, IDictionarySink<K, V>, IDictionarySource<K, V>, IMIndexed<K, V> { }

#endregion
}
24 changes: 12 additions & 12 deletions Core/Loyc.Interfaces/Collections/ICount.cs
@@ -1,21 +1,21 @@
using System;
using System;
using System.Collections.Generic;
using System.Text;

namespace Loyc.Collections
{
/// <summary>Combines <see cref="IReadOnlyCollection{T}"/> with related interfaces
/// <see cref="ICount"/> and <see cref="IIsEmpty"/>.</summary>
public interface ISource<out T> : IReadOnlyCollection<T>, ICount, IIsEmpty
{
/// <summary>Gets the number of items in the collection.</summary>
/// <remarks>This property exists only to resolve the supposed "ambiguity" between
/// <see cref="IReadOnlyCollection{T}.Count"/> and <see cref="ICount.Count"/>.
new int Count { get; }
}

/// <summary>Holds the Count property found in nearly all collection interfaces.</summary>
/// <remarks>
/// Microsoft has made this interface unusable by not defining it themselves in
/// .NET 4.5. Now that I've replaced my original interface
/// <code>
/// interface ISource&lt;out T> : IEnumerable&lt;T>, ICount {}
/// </code>
/// with Microsoft's IReadOnlyCollection(T), the compiler complains constantly about
/// "Ambiguity between IReadOnlyCollection(T).Count and ICount.Count". Eliminating
/// ICount from most places seems to be the only solution.
/// </remarks>
public interface ICount : IIsEmpty
public interface ICount
{
/// <summary>Gets the number of items in the collection.</summary>
int Count { get; }
Expand Down
88 changes: 88 additions & 0 deletions Core/Loyc.Interfaces/Collections/IDictionarySource.cs
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Loyc.Collections
{
/// <summary>Represents the essence of a dictionary, which returns a value given a key.</summary>
/// <typeparam name="K">Input type.</typeparam>
/// <typeparam name="V">Output type.</typeparam>
/// <remarks>Consider implementing <see cref="ITryGet{K, V}"/> instead, or in addition
/// to this interface alone.</remarks>
public interface IIndexed<in K, out V>
{
/// <summary>Gets the value associated with the specified key.</summary>
/// <exception cref="KeyNotFoundException">The key was not found.</exception>
/// <exception cref="ArgumentOutOfRangeException">The class implements <see cref="IReadOnlyList{V}"/>
/// and the key is an integer index that is outside the valid range.</exception>
/// <exception cref="IndexOutOfRangeException">The object is an array or other list,
/// and the key is an integer index that is outside the valid range.</exception>
V this[K key] { get; }
}

/// <summary>Enables access to TryGet extension methods for retrieving items from
/// a collection without risk of exceptions. Unlike <see cref="IReadOnlyDictionary{K, V}"/>,
/// this interface supports variance (e.g. ITryGet{object, string} can be assigned to
/// ITryGet{string, object}) and it does not throw when the key is null.</summary>
public interface ITryGet<in K, out V>
{
/// <summary>Gets the item for the specified key or index, and does not throw an
/// exception on failure.</summary>
/// <param name="key">A lookup key that might be associated with a value in this
/// object. If K is an integer, this value could be an index into a list.</param>
/// <param name="fail">TryGet sets this to true on failure or false on success.</param>
/// <returns>The element at the specified index, or default(V) if the index
/// is not valid.</returns>
/// <remarks>
/// This method must not throw if key == null, although it may use third-party
/// methods that throw (e.g. Object.Equals()).
/// <para/>
/// Ideally the return type would be <see cref="Maybe{T}"/>, but that design would
/// not allow variance on the output type (out V). Instead, an extension method
/// <see cref="TryGetExt.TryGet{K, V}(ITryGet{K, V}, K)"/> is provided that returns
/// <see cref="Maybe{V}"/>.
/// </remarks>
V TryGet(K key, out bool fail);
}

/// <summary>Standard extension methods for <see cref="ITryGet{K, V}"/>.</summary>
public static class TryGetExt
{
/// <summary>Returns the value at the specified key or index, wrapped in
/// <see cref="Maybe{V}"/>.</summary>
/// <param name="key">A lookup key that might be associated with a value in this
/// object. If K is an integer, this value could be an index into a list.</param>
/// <returns>A value associated with the key, wrapped in <see cref="Maybe{V}"/>
/// so that <see cref="Maybe{V}.HasValue"/> is false if lookup fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Maybe<V> TryGet<K, V>(this ITryGet<K, V> self, K key)
{
V value = self.TryGet(key, out bool fail);
return fail ? default(Maybe<V>) : new Maybe<V>(value);
}

/// <summary>Returns the value at the specified key or index, or the specified
/// default value if the key was not found.</summary>
/// <param name="key">A lookup key that might be associated with a value in this
/// object. If K is an integer, this value could be an index into a list.</param>
/// <param name="defaultValue">A value to return if lookup fails.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static V TryGet<K, V>(this ITryGet<K, V> self, K key, V defaultValue)
{
V value = self.TryGet(key, out bool fail);
return fail ? defaultValue : value;
}
}

/// <summary>Combines <see cref="IReadOnlyDictionary{K, V}"/> with related interfaces
/// <see cref="IIndexed{K, V}"/>, <see cref="ITryGet{K, V}"/> and
/// <see cref="ISource{KeyValuePair{K,V}}"/>.</summary>
/// <typeparam name="K">Used for lookups</typeparam>
/// <typeparam name="V">Type of value associated with each key</typeparam>
public interface IDictionarySource<K, V> : IReadOnlyDictionary<K, V>, IIndexed<K, V>, ITryGet<K, V>, ISource<KeyValuePair<K, V>>
{
}
}
2 changes: 1 addition & 1 deletion Core/Loyc.Interfaces/Collections/IListSource.cs
Expand Up @@ -42,7 +42,7 @@ namespace Loyc.Collections
/// Using <see cref="Impl.ListSourceBase{T}"/> as your base class can help you
/// implement this interface more quickly.
/// </remarks>
public interface IListSource<out T> : IReadOnlyList<T>, ITryGet<int, T>, IIndexed<int, T>
public interface IListSource<out T> : IReadOnlyList<T>, ISource<T>, ITryGet<int, T>, IIndexed<int, T>
{
/// <summary>Returns a sub-range of this list.</summary>
/// <param name="start">The new range will start at this index in the current
Expand Down
70 changes: 6 additions & 64 deletions Core/Loyc.Interfaces/Collections/Other interfaces.cs
Expand Up @@ -33,72 +33,14 @@ public interface IArray<T> : IListSource<T>, IArraySink<T>
bool TrySet(int index, T value);
}

/// <summary>Represents the essence of a dictionary, which returns a value given a key.</summary>
/// <typeparam name="K">Input type.</typeparam>
/// <typeparam name="V">Output type.</typeparam>
/// <remarks>Consider implementing <see cref="ITryGet{K, V}"/> instead, or in addition
/// to this interface alone.</remarks>
public interface IIndexed<in K, out V>
/// <summary>Represents the essence of a mutable dictionary: the ability to get or set
/// a value for a key.</summary>
public interface IMIndexed<in K, V> : IIndexed<K, V>, IIndexedSink<K, V>
{
/// <summary>Gets the value associated with the specified key.</summary>
/// <exception cref="KeyNotFoundException">The key was not found.</exception>
/// <exception cref="ArgumentOutOfRangeException">The class implements <see cref="IReadOnlyList{V}"/>
/// and the key is an integer index that is outside the valid range.</exception>
/// <exception cref="IndexOutOfRangeException">The object is an array or other list,
/// and the key is an integer index that is outside the valid range.</exception>
V this[K key] { get; }
}

/// <summary>Enables access to TryGet extension methods for retrieving items from
/// a collection without risk of exceptions.</summary>
public interface ITryGet<in K, out V>
{
/// <summary>Gets the item for the specified key or index, and does not throw an
/// exception on failure.</summary>
/// <param name="key">A lookup key that might be associated with a value in this
/// object. If K is an integer, this value could be an index into a list.</param>
/// <param name="fail">TryGet sets this to true on failure or false on success.</param>
/// <returns>The element at the specified index, or default(V) if the index
/// is not valid.</returns>
/// <remarks>
/// This method should never intentionally throw (e.g. don't throw if key == null)
/// although it may use third-party methods that throw (e.g. Object.Equals()).
/// <para/>
/// Ideally the return type would be <see cref="Maybe{T}"/> but that design would
/// not allow variance on the output type (out V). Instead, an extension method
/// <see cref="TryGetExt.TryGet{K, V}(ITryGet{K, V}, K)"/> is provided that returns
/// <see cref="Maybe{V}"/>.
/// </remarks>
V TryGet(K key, out bool fail);
}

/// <summary>Standard extension methods for <see cref="ITryGet{K, V}"/>.</summary>
public static class TryGetExt
{
/// <summary>Returns the value at the specified key or index, wrapped in
/// <see cref="Maybe{V}"/>.</summary>
/// <param name="key">A lookup key that might be associated with a value in this
/// object. If K is an integer, this value could be an index into a list.</param>
/// <returns>A value associated with the key, wrapped in <see cref="Maybe{V}"/>
/// so that <see cref="Maybe{V}.HasValue"/> is false if lookup fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Maybe<V> TryGet<K, V>(this ITryGet<K, V> self, K key)
{
V value = self.TryGet(key, out bool fail);
return fail ? default(Maybe<V>) : new Maybe<V>(value);
}

/// <summary>Returns the value at the specified key or index, or the specified
/// default value if the key was not found.</summary>
/// <param name="key">A lookup key that might be associated with a value in this
/// object. If K is an integer, this value could be an index into a list.</param>
/// <param name="defaultValue">A value to return if lookup fails.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static V TryGet<K, V>(this ITryGet<K, V> self, K key, V defaultValue)
{
V value = self.TryGet(key, out bool fail);
return fail ? defaultValue : value;
}
/// <remarks>This property should not exist. It exists only to resolve the
/// supposed "ambiguity" between the getter and setter in the base interfaces.</remarks>
new V this[K key] { get; set; }
}

/// <summary>Interface for an Optimize() method.</summary>
Expand Down
7 changes: 5 additions & 2 deletions Core/Loyc.Interfaces/Concrete/BaseDictionary.cs
Expand Up @@ -28,9 +28,12 @@ public abstract class DictionaryBase<TKey, TValue> : IDictionaryAndReadOnly<TKey
/// in C# so a separate method is required.</remarks>
protected abstract void SetValue(TKey key, TValue value);

public bool IsReadOnly
public bool IsReadOnly => false;
public bool IsEmpty => Count != 0;
public TValue TryGet(TKey key, out bool fail)
{
get { return false; }
fail = !TryGetValue(key, out TValue value);
return value;
}

public ICollection<TKey> Keys
Expand Down

0 comments on commit 56a6a00

Please sign in to comment.