Skip to content

Commit

Permalink
✨ Add Dictionary Extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
desjoerd committed Jan 7, 2025
1 parent e9266bf commit 64ac6b4
Show file tree
Hide file tree
Showing 3 changed files with 364 additions and 0 deletions.
64 changes: 64 additions & 0 deletions src/OptionalValues/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace OptionalValues.Extensions;

/// <summary>
/// Extension methods for <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// Gets the value associated with the specified key, returning <see cref="OptionalValue{T}.Unspecified"/> if the key is not found.
/// </summary>
/// <returns>A specified <see cref="OptionalValue{T}"/> when the key is found, or an <see cref="OptionalValue{T}.Unspecified"/> when the key is not found.</returns>
public static OptionalValue<T> GetOptionalValue<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key)
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(key);

return dictionary.TryGetValue(key, out T? value) ? new OptionalValue<T>(value) : OptionalValue<T>.Unspecified;
}

/// <summary>
/// Adds the specified key and value to the dictionary if the value is specified.
/// </summary>
/// <exception cref="ArgumentException">When value is specified and the key already exists in the dictionary.</exception>
/// <exception cref="ArgumentNullException">The key is null.</exception>
public static void AddOptionalValue<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key, OptionalValue<T> value)
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(key);

if (value.IsSpecified)
{
dictionary.Add(key, value.SpecifiedValue);
}
}

/// <summary>
/// Adds the specified key and value to the dictionary if the value is specified.
/// </summary>
/// <exception cref="ArgumentException">When value is specified and the key already exists in the dictionary.</exception>
/// <exception cref="ArgumentNullException">The key is null.</exception>
/// <returns><see langword="true"/> if the value was added to the dictionary; otherwise when the <paramref name="key"/> already exists or the value is <see cref="OptionalValue{T}.Unspecified"/>, <see langword="false"/>.</returns>
public static bool TryAddOptionalValue<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key, OptionalValue<T> value)
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(key);

return value.IsSpecified && dictionary.TryAdd(key, value.SpecifiedValue);
}

/// <summary>
/// Sets the specified key and value in the dictionary if the value is specified.
/// </summary>
/// <exception cref="ArgumentNullException">The key is null.</exception>
public static void SetOptionalValue<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key, OptionalValue<T> value)
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(key);

if (value.IsSpecified)
{
dictionary[key] = value.SpecifiedValue;
}
}
}
5 changes: 5 additions & 0 deletions src/OptionalValues/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
OptionalValues.Extensions.DictionaryExtensions
OptionalValues.OptionalValueJsonConverterAttribute
OptionalValues.OptionalValueJsonConverterAttribute.InnerConverterType.get -> System.Type?
OptionalValues.OptionalValueJsonConverterAttribute.OptionalValueJsonConverterAttribute() -> void
OptionalValues.OptionalValueJsonConverterAttribute.OptionalValueJsonConverterAttribute(System.Type! innerConverterType) -> void
override OptionalValues.OptionalValueJsonConverterAttribute.CreateConverter(System.Type! typeToConvert) -> System.Text.Json.Serialization.JsonConverter?
static OptionalValues.Extensions.DictionaryExtensions.AddOptionalValue<TKey, T>(this System.Collections.Generic.IDictionary<TKey, T>! dictionary, TKey key, OptionalValues.OptionalValue<T> value) -> void
static OptionalValues.Extensions.DictionaryExtensions.GetOptionalValue<TKey, T>(this System.Collections.Generic.IDictionary<TKey, T>! dictionary, TKey key) -> OptionalValues.OptionalValue<T>
static OptionalValues.Extensions.DictionaryExtensions.SetOptionalValue<TKey, T>(this System.Collections.Generic.IDictionary<TKey, T>! dictionary, TKey key, OptionalValues.OptionalValue<T> value) -> void
static OptionalValues.Extensions.DictionaryExtensions.TryAddOptionalValue<TKey, T>(this System.Collections.Generic.IDictionary<TKey, T>! dictionary, TKey key, OptionalValues.OptionalValue<T> value) -> bool
virtual OptionalValues.OptionalValueJsonConverterAttribute.CreateInnerConverter() -> System.Text.Json.Serialization.JsonConverter!
295 changes: 295 additions & 0 deletions test/OptionalValues.Tests/Extensions/DictionaryExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
using OptionalValues.Extensions;

namespace OptionalValues.Tests.Extensions;

public class DictionaryExtensionsTest
{
public class GetOptionalValue : DictionaryExtensionsTest
{
[Fact]
public void ReturnsUnspecifiedWhenKeyIsNotFound()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
OptionalValue<int> result = dictionary.GetOptionalValue("key");

// Assert
Assert.False(result.IsSpecified);
}

[Fact]
public void ReturnsSpecifiedValueWhenKeyIsFound()
{
// Arrange
var dictionary = new Dictionary<string, int>
{
["key"] = 42
};

// Act
OptionalValue<int> result = dictionary.GetOptionalValue("key");

// Assert
Assert.True(result.IsSpecified);
Assert.Equal(42, result.SpecifiedValue);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenDictionaryIsNull()
{
// Arrange
IDictionary<string, int> dictionary = null!;

// Act
Action action = () => dictionary.GetOptionalValue("key");

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("dictionary", exception.ParamName);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenKeyIsNull()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
Action action = () => dictionary.GetOptionalValue(null!);

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("key", exception.ParamName);
}
}

public class AddOptionalValue : DictionaryExtensionsTest
{
[Fact]
public void AddsValueWhenSpecified()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
dictionary.AddOptionalValue("key", new OptionalValue<int>(42));

// Assert
Assert.Equal(42, dictionary["key"]);
}

[Fact]
public void DoesNotAddValueWhenUnspecified()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
dictionary.AddOptionalValue("key", OptionalValue<int>.Unspecified);

// Assert
Assert.Empty(dictionary);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenDictionaryIsNull()
{
// Arrange
IDictionary<string, int> dictionary = null!;

// Act
Action action = () => dictionary.AddOptionalValue("key", new OptionalValue<int>(42));

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("dictionary", exception.ParamName);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenKeyIsNull()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
Action action = () => dictionary.AddOptionalValue(null!, new OptionalValue<int>(42));

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("key", exception.ParamName);
}

[Fact]
public void ThrowsArgumentExceptionWhenKeyAlreadyExists()
{
// Arrange
var dictionary = new Dictionary<string, int>
{
["key"] = 42
};

// Act
Action action = () => dictionary.AddOptionalValue("key", new OptionalValue<int>(42));

// Assert
Assert.Throws<ArgumentException>(action);
}
}

public class TryAddOptionalValue : DictionaryExtensionsTest
{
[Fact]
public void AddsValueWhenSpecified()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
var result = dictionary.TryAddOptionalValue("key", new OptionalValue<int>(42));

// Assert
Assert.True(result);
Assert.Equal(42, dictionary["key"]);
}

[Fact]
public void DoesNotAddValueWhenUnspecified()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
var result = dictionary.TryAddOptionalValue("key", OptionalValue<int>.Unspecified);

// Assert
Assert.False(result);
Assert.Empty(dictionary);
}

[Fact]
public void DoesNotAddWhenKeyExists()
{
// Arrange
var dictionary = new Dictionary<string, int>
{
["key"] = 42
};

// Act
var result = dictionary.TryAddOptionalValue("key", new OptionalValue<int>(9000));

// Assert
Assert.False(result);
Assert.Equal(42, dictionary["key"]);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenDictionaryIsNull()
{
// Arrange
IDictionary<string, int> dictionary = null!;

// Act
Action action = () => dictionary.TryAddOptionalValue("key", new OptionalValue<int>(42));

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("dictionary", exception.ParamName);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenKeyIsNull()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
Action action = () => dictionary.TryAddOptionalValue(null!, new OptionalValue<int>(42));

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("key", exception.ParamName);
}
}

public class SetOptionalValue : DictionaryExtensionsTest
{
[Fact]
public void SetsValueWhenSpecified()
{
// Arrange
var dictionary = new Dictionary<string, int>
{
["key"] = 42
};

// Act
dictionary.SetOptionalValue("key", new OptionalValue<int>(9000));

// Assert
Assert.Equal(9000, dictionary["key"]);
}

[Fact]
public void DoesNotSetValueWhenUnspecified()
{
// Arrange
var dictionary = new Dictionary<string, int>
{
["key"] = 42
};

// Act
dictionary.SetOptionalValue("key", OptionalValue<int>.Unspecified);

// Assert
Assert.Equal(42, dictionary["key"]);
}

[Fact]
public void OverwritesValueWhenSpecified()
{
// Arrange
var dictionary = new Dictionary<string, int>
{
["key"] = 42
};

// Act
dictionary.SetOptionalValue("key", new OptionalValue<int>(9000));

// Assert
Assert.Equal(9000, dictionary["key"]);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenDictionaryIsNull()
{
// Arrange
IDictionary<string, int> dictionary = null!;

// Act
Action action = () => dictionary.SetOptionalValue("key", new OptionalValue<int>(42));

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("dictionary", exception.ParamName);
}

[Fact]
public void ThrowsArgumentNullExceptionWhenKeyIsNull()
{
// Arrange
var dictionary = new Dictionary<string, int>();

// Act
Action action = () => dictionary.SetOptionalValue(null!, new OptionalValue<int>(42));

// Assert
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(action);
Assert.Equal("key", exception.ParamName);
}
}
}

0 comments on commit 64ac6b4

Please sign in to comment.