Separate unwrapped Enum.Next/Previous to Unchecked overloads

This commit is contained in:
Oliver Booth 2022-04-20 18:16:59 +01:00
parent d9b60fbb94
commit facc0834f0
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
3 changed files with 122 additions and 26 deletions

View File

@ -0,0 +1,61 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace X10D.Tests.Core;
[TestClass]
public class EnumTests
{
// Microsoft wrongfully decided to have Sunday be 0, Monday be 1, etc.
// I personally hate this, Sunday is not the first day of the week.
// it's clearly Monday as defined by ISO 8601.
// but Microsoft can't fix this without breaking compatibility.
// I have feelings...
[TestMethod]
public void Next()
{
Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Sunday.Next());
Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Monday.Next());
Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Tuesday.Next());
Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Wednesday.Next());
Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Thursday.Next());
Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Friday.Next());
Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Saturday.Next()); // Saturday is the "last" day. wrap to "first"
}
[TestMethod]
public void NextUnchecked()
{
Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Sunday.NextUnchecked());
Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Monday.NextUnchecked());
Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Tuesday.NextUnchecked());
Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Wednesday.NextUnchecked());
Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Thursday.NextUnchecked());
Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Friday.NextUnchecked());
Assert.ThrowsException<IndexOutOfRangeException>(() => DayOfWeek.Saturday.NextUnchecked());
}
[TestMethod]
public void Previous()
{
Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Sunday.Previous()); // Sunday is the "first" day. wrap to "last"
Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Monday.Previous());
Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Tuesday.Previous());
Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Wednesday.Previous());
Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Thursday.Previous());
Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Friday.Previous());
Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Saturday.Previous());
}
[TestMethod]
public void PreviousUnchecked()
{
Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Monday.PreviousUnchecked());
Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Tuesday.PreviousUnchecked());
Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Wednesday.PreviousUnchecked());
Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Thursday.PreviousUnchecked());
Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Friday.PreviousUnchecked());
Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Saturday.PreviousUnchecked());
Assert.ThrowsException<IndexOutOfRangeException>(() => DayOfWeek.Sunday.PreviousUnchecked());
}
}

View File

@ -9,6 +9,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cdoubleextensions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cdoubleextensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cendpointextensions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cendpointextensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cenumerableextensions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cenumerableextensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cenumextensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cfileinfoextensions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cfileinfoextensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cint16extensions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cint16extensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cint32extensions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=src_005Cint32extensions/@EntryIndexedValue">True</s:Boolean>

View File

@ -6,44 +6,78 @@
public static class EnumExtensions public static class EnumExtensions
{ {
/// <summary> /// <summary>
/// Returns the next member defined in a specified enum. /// Returns the value which is defined proceeding this value in the enumeration.
/// </summary> /// </summary>
/// <typeparam name="T">The enum type.</typeparam> /// <typeparam name="T">The type of the enumeration.</typeparam>
/// <param name="source">The enum value which should be used as the starting point.</param> /// <param name="value">The value whose proceeding value to retrieve.</param>
/// <param name="wrap">
/// Optional. <see langword="true" /> if the final value of <typeparamref name="T" /> should wrap around to the first
/// value; otherwise, <see langword="false" />. Defaults to <see langword="true" />.
/// </param>
/// <returns> /// <returns>
/// A value of <typeparamref name="T" /> that is considered to be the next value defined after /// A value of <typeparamref name="T" /> that is considered to be the next value defined after <paramref name="value" />,
/// <paramref name="source" />. /// or the first value if <paramref name="value" /> is the final field of the enumeration.
/// </returns> /// </returns>
public static T Next<T>(this T source, bool wrap = true) public static T Next<T>(this T value)
where T : struct, Enum where T : struct, Enum
{ {
var array = Enum.GetValues<T>(); T[] values = Enum.GetValues<T>();
int index = Array.IndexOf(array, source) + 1; int index = Array.IndexOf(values, value) + 1;
return array.Length == index ? array[wrap ? 0 : index - 1] : array[index]; index %= values.Length;
return values[index];
} }
/// <summary> /// <summary>
/// Returns the previous member defined in a specified enum. /// Returns the value which is defined proceeding this value in the enumeration.
/// </summary> /// </summary>
/// <typeparam name="T">The enum type.</typeparam> /// <typeparam name="T">The type of the enumeration.</typeparam>
/// <param name="source">The enum value which should be used as the starting point.</param> /// <param name="value">The value whose proceeding value to retrieve.</param>
/// <param name="wrap">
/// Optional. <see langword="true" /> if the first value of <typeparamref name="T" /> should wrap around to the final
/// value; otherwise, <see langword="false" />. Defaults to <see langword="true" />.
/// </param>
/// <returns> /// <returns>
/// A value of <typeparamref name="T" /> that is considered to be the previous value defined before /// A value of <typeparamref name="T" /> that is considered to be the next value defined after
/// <paramref name="source" />. /// <paramref name="value" />.
/// </returns> /// </returns>
public static T Previous<T>(this T source, bool wrap = true) /// <exception cref="IndexOutOfRangeException"><paramref name="value" /> is the final field of the enumeration.</exception>
public static T NextUnchecked<T>(this T value)
where T : struct, Enum where T : struct, Enum
{ {
var array = Enum.GetValues<T>(); T[] values = Enum.GetValues<T>();
int index = Array.IndexOf(array, source) - 1; int index = Array.IndexOf(values, value) + 1;
return index < 0 ? array[wrap ? array.Length - 1 : 0] : array[index]; return values[index];
}
/// <summary>
/// Returns the value which is defined preceeding this value in the enumeration.
/// </summary>
/// <typeparam name="T">The type of the enumeration.</typeparam>
/// <param name="value">The value whose preceeding value to retrieve.</param>
/// <returns>
/// A value of <typeparamref name="T" /> that is considered to be the previous value defined after
/// <paramref name="value" />, or the last value if <paramref name="value" /> is the first field of the enumeration.
/// </returns>
public static T Previous<T>(this T value)
where T : struct, Enum
{
T[] values = Enum.GetValues<T>();
int index = Array.IndexOf(values, value) - 1;
int length = values.Length;
// negative modulo is not supported in C#. workaround: https://stackoverflow.com/a/1082938/1467293
// sure, simply checking for index < 0 is enough, but this expression is so fucking cool!
index = (index % length + length) % length;
return values[index];
}
/// <summary>
/// Returns the value which is defined preceeding this value in the enumeration.
/// </summary>
/// <typeparam name="T">The type of the enumeration.</typeparam>
/// <param name="value">The value whose preceeding value to retrieve.</param>
/// <returns>
/// A value of <typeparamref name="T" /> that is considered to be the previous value defined after
/// <paramref name="value" />, or the last value if <paramref name="value" /> is the first field of the enumeration.
/// </returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="value" /> is the first field of the enumeration.</exception>
public static T PreviousUnchecked<T>(this T value)
where T : struct, Enum
{
T[] values = Enum.GetValues<T>();
int index = Array.IndexOf(values, value) - 1;
return values[index];
} }
} }