├── Strilanc.Value.May.1.0.0.nupkg ├── Strilanc.Value.May.1.0.1.nupkg ├── Strilanc.Value.May.1.0.2.nupkg ├── .gitignore ├── MayExample ├── App.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── App.xaml ├── App.xaml.cs ├── MainWindow.xaml ├── ExampleMayUtilityMethods.cs ├── MainWindow.xaml.cs └── MayExample.csproj ├── ReadMe.txt ├── License.txt ├── May ├── IMayHaveValue.cs ├── Properties │ └── AssemblyInfo.cs ├── MayNoValue.cs ├── May.csproj ├── May.cs ├── MayUtilities.cs └── MayExtensions.cs ├── MayTest ├── TestUtil.cs ├── Properties │ └── AssemblyInfo.cs ├── MayUtilitiesTest.cs ├── MayTest.csproj └── MayTest.cs └── May.sln /Strilanc.Value.May.1.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Strilanc/May/HEAD/Strilanc.Value.May.1.0.0.nupkg -------------------------------------------------------------------------------- /Strilanc.Value.May.1.0.1.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Strilanc/May/HEAD/Strilanc.Value.May.1.0.1.nupkg -------------------------------------------------------------------------------- /Strilanc.Value.May.1.0.2.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Strilanc/May/HEAD/Strilanc.Value.May.1.0.2.nupkg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TestResults/* 2 | */bin/* 3 | */obj/* 4 | *.pfx 5 | *.snk 6 | *.testsettings 7 | *.suo 8 | *.crunchsolution.cache 9 | *.ncrunchsolution 10 | *.suo 11 | *.user 12 | _ReSharper.* 13 | -------------------------------------------------------------------------------- /MayExample/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MayExample/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MayExample/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MayExample/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace MayExample { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ReadMe.txt: -------------------------------------------------------------------------------- 1 | Implements an option type (Strilanc.Value.May) that encourages usage based on pattern matching rather than ForceGetValue. Also includes utility methods for producing, consuming and transforming May. 2 | 3 | Note on null: May treats null like any other value. May.NoValue is distinct from null, and both are distinct from ((object)null).Maybe(). 4 | 5 | Blog post with usage examples: http://twistedoakstudios.com/blog/Post1130_when-null-is-not-enough-an-option-type-for-c 6 | 7 | NuGet package: https://nuget.org/packages/Strilanc.Value.May -------------------------------------------------------------------------------- /MayExample/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Public Domain 2 | 3 | I place no restrictions on the use of code in this repostory, including: 4 | - The 'May' option type and related utility methods in the 'May' project 5 | - The examples and related code in the example project 6 | - The tests and related code in the testing project 7 | 8 | That means: 9 | - Attribution not required. 10 | - Commercial use allowed. 11 | - Derivative use allowed. 12 | 13 | But note: 14 | - No guarantees (I did my best to ensure correctness, but I am not infallible.). 15 | 16 | Not that I wouldn't mind hearing about it if you used my code. 17 | If you need a 'real' license for obtuse legal reasons, I would be happy to grant it. 18 | 19 | Craig Gidney, 2012 20 | -------------------------------------------------------------------------------- /May/IMayHaveValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics.Contracts; 4 | 5 | namespace Strilanc.Value { 6 | /// 7 | ///A potential value that may or may not contain an unknown value of unknown type. 8 | ///All implementations should compare equal and have a hash code of 0 when HasValue is false. 9 | /// 10 | /// 11 | ///Used to allow comparisons of the raw May.NoValue to generic ones like May<int>.NoValue. 12 | ///Also used as the result type of the 'do action if value present' method, but only because there is no standard void or unit type. 13 | /// 14 | [EditorBrowsable(EditorBrowsableState.Never)] 15 | public interface IMayHaveValue : IEquatable { 16 | ///Determines if this potential value contains a value or not. 17 | [Pure] 18 | bool HasValue { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MayExample/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18010 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MayExample.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /May/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("May")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("May")] 14 | [assembly: AssemblyCopyright("Copyright © 2012")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: NeutralResourcesLanguage("en")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0.0")] 31 | -------------------------------------------------------------------------------- /MayTest/TestUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | internal static class TestUtil { 7 | public static IEnumerable Range(this int count) { 8 | return Enumerable.Range(0, count); 9 | } 10 | public static void AssertThrows(Func func) where TException : Exception { 11 | AssertThrows(new Action(() => func())); 12 | } 13 | public static void AssertThrows(Action action) where TException : Exception { 14 | try { 15 | action(); 16 | Assert.Fail("Expected an exception of type {0}, but no exception was thrown.", 17 | typeof(TException).FullName); 18 | } catch (TException) { 19 | // pass! 20 | } catch (Exception ex) { 21 | Assert.Fail("Expected an exception of type {0}, but received one of type {1}: {2}.", 22 | typeof(TException).FullName, 23 | ex.GetType().FullName, 24 | ex); 25 | } 26 | } 27 | public static void AssertEquals(this T1 actual, T2 expected) { 28 | Assert.AreEqual(actual: actual, expected: expected); 29 | } 30 | public static void AssertIsTrue(this bool value) { 31 | Assert.IsTrue(value); 32 | } 33 | public static void AssertIsFalse(this bool value) { 34 | Assert.IsFalse(value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MayTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MayTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MayTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3bb9faf6-5874-4b06-82d7-305bfd4b8390")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /MayExample/ExampleMayUtilityMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Strilanc.Value; 4 | 5 | namespace MayExample { 6 | public static class ExampleMayUtilityMethods { 7 | ///Returns the signed 32bit integer represented by the given string, if there is one. 8 | public static May MayParseInt32(this string text) { 9 | int result; 10 | if (!Int32.TryParse(text, out result)) return May.NoValue; 11 | return result; 12 | } 13 | ///Returns the unsigned 32bit integer represented by the given string, if there is one. 14 | public static May MayParseUInt32(this string text) { 15 | uint result; 16 | if (!UInt32.TryParse(text, out result)) return May.NoValue; 17 | return result; 18 | } 19 | ///Returns the double represented by the given string, if there is one. 20 | public static May MayParseDouble(this string text) { 21 | double result; 22 | if (!Double.TryParse(text, out result)) return May.NoValue; 23 | return result; 24 | } 25 | ///Returns the big integer represented by the given string, if there is one. 26 | public static May MayParseBigInteger(this string text) { 27 | BigInteger result; 28 | if (!BigInteger.TryParse(text, out result)) return May.NoValue; 29 | return result; 30 | } 31 | 32 | ///Returns the non-negative square root of the given value, unless no real solution exists. 33 | public static May MaySqrt(this double value) { 34 | if (value >= 0) return Math.Sqrt(value); 35 | return May.NoValue; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /May.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "May", "May\May.csproj", "{37468DFC-8378-4F4F-94F2-39F5C1AB8C5A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MayTest", "MayTest\MayTest.csproj", "{B27038A2-7CFF-41B0-8FB6-DD38BBBD740C}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MayExample", "MayExample\MayExample.csproj", "{D7365D22-E675-4319-8A9C-779497B852AD}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {B27038A2-7CFF-41B0-8FB6-DD38BBBD740C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {B27038A2-7CFF-41B0-8FB6-DD38BBBD740C}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {B27038A2-7CFF-41B0-8FB6-DD38BBBD740C}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {B27038A2-7CFF-41B0-8FB6-DD38BBBD740C}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {D7365D22-E675-4319-8A9C-779497B852AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {D7365D22-E675-4319-8A9C-779497B852AD}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {D7365D22-E675-4319-8A9C-779497B852AD}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {D7365D22-E675-4319-8A9C-779497B852AD}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /May/MayNoValue.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics; 3 | using System.Diagnostics.Contracts; 4 | 5 | namespace Strilanc.Value { 6 | /// 7 | ///A non-generic lack-of-value type, equivalent to generic likes like lack-of-int. 8 | ///Use Strilanc.Value.May.NoValue to get an instance. 9 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 10 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 11 | /// 12 | [EditorBrowsable(EditorBrowsableState.Never)] 13 | [DebuggerDisplay("{ToString()}")] 14 | public struct MayNoValue : IMayHaveValue { 15 | ///Determines if this potential value contains a value or not (it doesn't). 16 | [Pure] 17 | public bool HasValue { get { return false; } } 18 | ///Returns the hash code for a lack of potential value. 19 | public override int GetHashCode() { 20 | return 0; 21 | } 22 | ///Determines if the given potential value contains no value. 23 | public bool Equals(IMayHaveValue other) { 24 | return other != null && !other.HasValue; 25 | } 26 | ///Determines if the given object is a potential value containing no value. 27 | public override bool Equals(object obj) { 28 | return Equals(obj as IMayHaveValue); 29 | } 30 | ///Determines if two lack of values are equal (they are). 31 | public static bool operator ==(MayNoValue noValue1, MayNoValue noValue2) { 32 | return true; 33 | } 34 | ///Determines if two lack of values are not equal (they're not). 35 | public static bool operator !=(MayNoValue noValue1, MayNoValue noValue2) { 36 | return false; 37 | } 38 | ///Returns a string representation of this lack of value. 39 | public override string ToString() { 40 | return "No Value"; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MayExample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("MayExample")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("MayExample")] 15 | [assembly: AssemblyCopyright("Copyright © 2012")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /MayExample/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18010 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MayExample.Properties { 12 | 13 | 14 | /// 15 | /// A strongly-typed resource class, for looking up localized strings, etc. 16 | /// 17 | // This class was auto-generated by the StronglyTypedResourceBuilder 18 | // class via a tool like ResGen or Visual Studio. 19 | // To add or remove a member, edit your .ResX file then rerun ResGen 20 | // with the /str option, or rebuild your VS project. 21 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources { 25 | 26 | private static global::System.Resources.ResourceManager resourceMan; 27 | 28 | private static global::System.Globalization.CultureInfo resourceCulture; 29 | 30 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 31 | internal Resources() { 32 | } 33 | 34 | /// 35 | /// Returns the cached ResourceManager instance used by this class. 36 | /// 37 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 38 | internal static global::System.Resources.ResourceManager ResourceManager { 39 | get { 40 | if ((resourceMan == null)) { 41 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MayExample.Properties.Resources", typeof(Resources).Assembly); 42 | resourceMan = temp; 43 | } 44 | return resourceMan; 45 | } 46 | } 47 | 48 | /// 49 | /// Overrides the current thread's CurrentUICulture property for all 50 | /// resource lookups using this strongly typed resource class. 51 | /// 52 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 53 | internal static global::System.Globalization.CultureInfo Culture { 54 | get { 55 | return resourceCulture; 56 | } 57 | set { 58 | resourceCulture = value; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MayTest/MayUtilitiesTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Strilanc.Value; 4 | using System.Linq; 5 | 6 | [TestClass] 7 | public class MayUtilitiesTest { 8 | [TestMethod] 9 | public void MayAggregate() { 10 | 0.Range().MayAggregate((e1, e2) => e1 + e2).AssertEquals(May.NoValue); 11 | 2.Range().MayAggregate((e1, e2) => e1 * e2).AssertEquals(0.Maybe()); 12 | 5.Range().MayAggregate((e1, e2) => e1 + e2).AssertEquals(10.Maybe()); 13 | } 14 | [TestMethod] 15 | public void MayMin() { 16 | 0.Range().MayMin().AssertEquals(May.NoValue); 17 | 2.Range().MayMin().AssertEquals(0.Maybe()); 18 | 4.Range().Skip(1).Reverse().MayMin().AssertEquals(1.Maybe()); 19 | } 20 | [TestMethod] 21 | public void MayMax() { 22 | 0.Range().MayMax().AssertEquals(May.NoValue); 23 | 2.Range().MayMax().AssertEquals(1.Maybe()); 24 | 4.Range().Skip(1).Reverse().MayMax().AssertEquals(3.Maybe()); 25 | } 26 | [TestMethod] 27 | public void MayMinBy() { 28 | 0.Range().MayMinBy(e => -e).AssertEquals(May.NoValue); 29 | 2.Range().MayMinBy(e => -e).AssertEquals(1.Maybe()); 30 | 4.Range().Skip(1).Reverse().MayMinBy(e => -e).AssertEquals(3.Maybe()); 31 | } 32 | [TestMethod] 33 | public void MayMaxBy() { 34 | 0.Range().MayMaxBy(e => -e).AssertEquals(May.NoValue); 35 | 2.Range().MayMaxBy(e => -e).AssertEquals(0.Maybe()); 36 | 4.Range().Skip(1).Reverse().MayMaxBy(e => -e).AssertEquals(1.Maybe()); 37 | } 38 | [TestMethod] 39 | public void MayFirst() { 40 | 0.Range().MayFirst().AssertEquals(May.NoValue); 41 | 1.Range().MayFirst().AssertEquals(0.Maybe()); 42 | 2.Range().MayFirst().AssertEquals(0.Maybe()); 43 | } 44 | [TestMethod] 45 | public void MayLast() { 46 | 0.Range().MayLast().AssertEquals(May.NoValue); 47 | 1.Range().MayLast().AssertEquals(0.Maybe()); 48 | 2.Range().MayLast().AssertEquals(1.Maybe()); 49 | } 50 | [TestMethod] 51 | public void MaySingle() { 52 | 0.Range().MaySingle().AssertEquals(May.NoValue); 53 | 1.Range().MaySingle().AssertEquals(0.Maybe()); 54 | TestUtil.AssertThrows(() => 55 | 2.Range().MaySingle()); 56 | } 57 | [TestMethod] 58 | public void WhereHasValue() { 59 | 0.Range().Select(e => e.Maybe()).WhereHasValue().SequenceEqual(0.Range()).AssertIsTrue(); 60 | 10.Range().Select(e => e.Maybe()).WhereHasValue().SequenceEqual(10.Range()).AssertIsTrue(); 61 | 10.Range().Select(e => e == 5 ? May.NoValue : e.Maybe()).WhereHasValue().SequenceEqual(10.Range().Where(e => e != 5)).AssertIsTrue(); 62 | } 63 | [TestMethod] 64 | public void MayAll() { 65 | 0.Range().Select(e => e.Maybe()).MayAll().Select(e => e.SequenceEqual(0.Range())).AssertEquals(true.Maybe()); 66 | 10.Range().Select(e => e.Maybe()).MayAll().Select(e => e.SequenceEqual(10.Range())).AssertEquals(true.Maybe()); 67 | 10.Range().Select(e => e == 5 ? May.NoValue : e.Maybe()).MayAll().AssertEquals(May.NoValue); 68 | } 69 | [TestMethod] 70 | public void MayNullable() { 71 | 1.Maybe().AsNullable().AssertEquals(1); 72 | new May().AsNullable().AssertEquals((int?)null); 73 | ((int?)null).AsMay().AssertEquals(May.NoValue); 74 | ((int?)1).AsMay().AssertEquals(1.Maybe()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /MayExample/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Numerics; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Media; 7 | using Strilanc.Value; 8 | 9 | namespace MayExample { 10 | public partial class MainWindow { 11 | public MainWindow() { 12 | InitializeComponent(); 13 | 14 | // see ExampleMayUtilityMethods.cs for implementations of MayParseX, MaySqrt 15 | 16 | Show("A as BigInteger", (a, b) => 17 | a.MayParseBigInteger()); 18 | 19 | Show("B as BigInteger", (a, b) => 20 | b.MayParseBigInteger()); 21 | 22 | Show("A as Int32", (a, b) => 23 | a.MayParseInt32()); 24 | 25 | Show("A as UInt32", (a, b) => 26 | a.MayParseUInt32()); 27 | 28 | Show("A as Double", (a, b) => 29 | a.MayParseDouble()); 30 | 31 | Show("B as Double else NaN", (a, b) => 32 | b.MayParseDouble() 33 | .Else(Double.NaN)); 34 | 35 | Show("A + B (BigInteger)", (a, b) => 36 | from va in a.MayParseBigInteger() 37 | from vb in b.MayParseBigInteger() 38 | select va + vb); 39 | 40 | Show("A * B (BigInteger)", (a, b) => 41 | a.MayParseBigInteger() 42 | .Combine( 43 | b.MayParseBigInteger(), 44 | (va, vb) => va * vb)); 45 | 46 | Show("SquareRoot(A)", (a, b) => 47 | a.MayParseDouble() 48 | .Select(e => e.MaySqrt()) 49 | .Unwrap()); 50 | 51 | Show("SquareRoot(B)", (a, b) => 52 | b.MayParseDouble() 53 | .Bind(e => e.MaySqrt())); 54 | 55 | Show("SquareRoot(A) - SquareRoot(B)", (a, b) => 56 | from va in a.MayParseDouble() 57 | from vb in b.MayParseDouble() 58 | from sa in va.MaySqrt() 59 | from sb in vb.MaySqrt() 60 | select sa - sb); 61 | 62 | Show("First double in [A, B]", (a, b) => 63 | new[] { a, b } 64 | .Select(e => e.MayParseDouble()) 65 | .WhereHasValue() 66 | .MayFirst()); 67 | 68 | Show("[A, B] as IEnumerable", (a, b) => 69 | new[] { a, b } 70 | .Select(e => e.MayParseDouble()) 71 | .MayAll() 72 | .Select(e => "[" + String.Join(", ", e) + "]")); 73 | 74 | var passes = 0; 75 | var fails = 0; 76 | Show("# of times A was a double", (a, b) => { 77 | a.MayParseDouble() 78 | .IfHasValueThenDo(ignoredValue => passes += 1) 79 | .ElseDo(() => fails += 1); 80 | return passes + "/" + (passes + fails); 81 | }); 82 | 83 | var sum = BigInteger.Zero; 84 | Show("Sum of B each time it was a BigInteger", (a, b) => { 85 | b.MayParseBigInteger().IfHasValueThenDo(v => sum += v); 86 | return sum; 87 | }); 88 | } 89 | 90 | private void Show(string title, Func computation) { 91 | // create controls to show result 92 | var rowColor = Rows.Children.Count % 2 == 0 ? Color.FromArgb(0, 0, 0, 0) : Color.FromArgb(25, 0, 0, 0); 93 | var rowStack = new StackPanel { Orientation = Orientation.Horizontal, Background = new SolidColorBrush(rowColor) }; 94 | var titleBlock = new TextBlock { Width = 300, Text = title }; 95 | var valueBlock = new TextBlock(); 96 | rowStack.Children.Add(titleBlock); 97 | rowStack.Children.Add(valueBlock); 98 | Rows.Children.Add(rowStack); 99 | 100 | // show initial value 101 | Action update = () => valueBlock.Text = "" + computation(txtA.Text, txtB.Text); 102 | update(); 103 | 104 | // recompute when text changes 105 | TextChangedEventHandler h = (sender, arg) => update(); 106 | txtA.TextChanged += h; 107 | txtB.TextChanged += h; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /MayTest/MayTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {B27038A2-7CFF-41B0-8FB6-DD38BBBD740C} 7 | Library 8 | Properties 9 | MayTest 10 | MayTest 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A} 61 | May 62 | 63 | 64 | 65 | 66 | 67 | 68 | False 69 | 70 | 71 | False 72 | 73 | 74 | False 75 | 76 | 77 | False 78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /MayExample/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /May/May.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10.0 6 | Debug 7 | AnyCPU 8 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A} 9 | Library 10 | Properties 11 | Strilanc.Value 12 | May 13 | v4.0 14 | Profile136 15 | 512 16 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | 20 | 21 | 4.0 22 | 0 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | bin\Debug\May.XML 33 | False 34 | False 35 | True 36 | False 37 | False 38 | False 39 | True 40 | True 41 | True 42 | True 43 | True 44 | True 45 | True 46 | True 47 | False 48 | True 49 | False 50 | True 51 | False 52 | False 53 | False 54 | False 55 | True 56 | False 57 | True 58 | True 59 | True 60 | False 61 | False 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | True 70 | False 71 | False 72 | True 73 | Full 74 | Build 75 | 0 76 | 77 | 78 | pdbonly 79 | true 80 | bin\Release\ 81 | TRACE 82 | prompt 83 | 4 84 | bin\Release\May.XML 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 105 | -------------------------------------------------------------------------------- /May/May.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.Contracts; 4 | 5 | namespace Strilanc.Value { 6 | /// 7 | ///A potential value that may contain no value or may contain a value of type T. 8 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 9 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 10 | /// 11 | [DebuggerDisplay("{ToString()}")] 12 | public struct May : IMayHaveValue, IEquatable> { 13 | /// 14 | ///A potential value containing no value. 15 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 16 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 17 | /// 18 | [Pure] 19 | public static May NoValue { get { return default(May); } } 20 | 21 | private readonly T _value; 22 | private readonly bool _hasValue; 23 | ///Determines if this potential value contains a value or not. 24 | [Pure] 25 | public bool HasValue { get { return _hasValue; } } 26 | 27 | ///Constructs a potential value containing the given value. 28 | [Pure] 29 | public May(T value) { 30 | this._hasValue = true; 31 | this._value = value; 32 | } 33 | 34 | ///Matches this potential value into either a function expecting a value or a function expecting no value, returning the result. 35 | [Pure] 36 | public TOut Match(Func valueProjection, Func alternativeFunc) { 37 | if (valueProjection == null) throw new ArgumentNullException("valueProjection"); 38 | if (alternativeFunc == null) throw new ArgumentNullException("alternativeFunc"); 39 | return _hasValue ? valueProjection(_value) : alternativeFunc(); 40 | } 41 | 42 | ///Returns a potential value containing no value. 43 | public static implicit operator May(MayNoValue noValue) { 44 | return NoValue; 45 | } 46 | ///Returns a potential value containing the given value. 47 | public static implicit operator May(T value) { 48 | return new May(value); 49 | } 50 | ///Returns the value contained in the potential value, throwing a cast exception if the potential value contains no value. 51 | public static explicit operator T(May potentialValue) { 52 | if (!potentialValue._hasValue) throw new InvalidCastException("No Value"); 53 | return potentialValue._value; 54 | } 55 | 56 | ///Determines if two potential values are equivalent. 57 | public static bool operator ==(May potentialValue1, May potentialValue2) { 58 | return potentialValue1.Equals(potentialValue2); 59 | } 60 | ///Determines if two potential values are not equivalent. 61 | public static bool operator !=(May potentialValue1, May potentialValue2) { 62 | return !potentialValue1.Equals(potentialValue2); 63 | } 64 | 65 | ///Determines if two potential values are equivalent. 66 | public static bool operator ==(May potentialValue1, IMayHaveValue potentialValue2) { 67 | return potentialValue1.Equals(potentialValue2); 68 | } 69 | ///Determines if two potential values are not equivalent. 70 | public static bool operator !=(May potentialValue1, IMayHaveValue potentialValue2) { 71 | return !potentialValue1.Equals(potentialValue2); 72 | } 73 | 74 | ///Returns the hash code for this potential value. 75 | public override int GetHashCode() { 76 | return !_hasValue ? 0 77 | : ReferenceEquals(_value, null) ? -1 78 | : _value.GetHashCode(); 79 | } 80 | /// 81 | ///Determines if this potential value is equivalent to the given potential value. 82 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 83 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 84 | /// 85 | public bool Equals(May other) { 86 | if (other._hasValue != this._hasValue) return false; 87 | return !this._hasValue || Equals(_value, other._value); 88 | } 89 | /// 90 | ///Determines if this potential value is equivalent to the given potential value. 91 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 92 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 93 | /// 94 | public bool Equals(IMayHaveValue other) { 95 | if (other is May) return Equals((May)other); 96 | // potential values containing no value are always equal 97 | return other != null && !this.HasValue && !other.HasValue; 98 | } 99 | /// 100 | ///Determines if this potential value is equivalent to the given object. 101 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 102 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 103 | /// 104 | public override bool Equals(object obj) { 105 | if (obj is May) return Equals((May)obj); 106 | return Equals(obj as IMayHaveValue); 107 | } 108 | ///Returns a string representation of this potential value. 109 | public override string ToString() { 110 | return _hasValue 111 | ? String.Format("Value: {0}", _value) 112 | : "No Value"; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /May/MayUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | 6 | namespace Strilanc.Value { 7 | ///Utility methods that involve May<T> but with a focus on other types. 8 | public static class MayUtilities { 9 | ///Returns the value contained in the given potential value as a nullable type, returning null if there is no contained value. 10 | [Pure] 11 | public static T? AsNullable(this May potentialValue) where T : struct { 12 | return potentialValue.Select(e => (T?)e).ElseDefault(); 13 | } 14 | 15 | ///Returns the value contained in the given nullable value as a potential value, with null corresponding to no value. 16 | [Pure] 17 | public static May AsMay(this T? potentialValue) where T : struct { 18 | if (!potentialValue.HasValue) return May.NoValue; 19 | return potentialValue.Value; 20 | } 21 | 22 | /// 23 | /// Returns the result of using a folder function to combine all the items in the sequence into one aggregate item. 24 | /// If the sequence is empty, the result is NoValue. 25 | /// 26 | [Pure] 27 | public static May MayAggregate(this IEnumerable sequence, Func folder) { 28 | if (sequence == null) throw new ArgumentNullException("sequence"); 29 | if (folder == null) throw new ArgumentNullException("folder"); 30 | return sequence.Aggregate( 31 | May.NoValue, 32 | (a, e) => a.Match(v => folder(v, e), e)); 33 | } 34 | 35 | /// 36 | /// Returns the minimum value in a sequence, as determined by the given comparer or else the type's default comparer. 37 | /// If the sequence is empty, the result is NoValue. 38 | /// 39 | [Pure] 40 | public static May MayMin(this IEnumerable sequence, IComparer comparer = null) { 41 | if (sequence == null) throw new ArgumentNullException("sequence"); 42 | var c = comparer ?? Comparer.Default; 43 | return sequence.MayAggregate((e1, e2) => c.Compare(e1, e2) <= 0 ? e1 : e2); 44 | } 45 | 46 | /// 47 | /// Returns the maximum value in a sequence, as determined by the given comparer or else the type's default comparer. 48 | /// If the sequence is empty, the result is NoValue. 49 | /// 50 | [Pure] 51 | public static May MayMax(this IEnumerable sequence, IComparer comparer = null) { 52 | if (sequence == null) throw new ArgumentNullException("sequence"); 53 | var c = comparer ?? Comparer.Default; 54 | return sequence.MayAggregate((e1, e2) => c.Compare(e1, e2) >= 0 ? e1 : e2); 55 | } 56 | 57 | /// 58 | /// Returns the minimum value in a sequence, as determined by projecting the items and using the given comparer or else the type's default comparer. 59 | /// If the sequence is empty, the result is NoValue. 60 | /// 61 | [Pure] 62 | public static May MayMinBy(this IEnumerable sequence, Func projection, IComparer comparer = null) { 63 | if (sequence == null) throw new ArgumentNullException("sequence"); 64 | var c = comparer ?? Comparer.Default; 65 | return sequence 66 | .Select(e => new { v = e, p = projection(e)}) 67 | .MayAggregate((e1, e2) => c.Compare(e1.p, e2.p) <= 0 ? e1 : e2) 68 | .Select(e => e.v); 69 | } 70 | 71 | /// 72 | /// Returns the maximum value in a sequence, as determined by projecting the items and using the given comparer or else the type's default comparer. 73 | /// If the sequence is empty, the result is NoValue. 74 | /// 75 | [Pure] 76 | public static May MayMaxBy(this IEnumerable sequence, Func projection, IComparer comparer = null) { 77 | if (sequence == null) throw new ArgumentNullException("sequence"); 78 | var c = comparer ?? Comparer.Default; 79 | return sequence 80 | .Select(e => new { v = e, p = projection(e) }) 81 | .MayAggregate((e1, e2) => c.Compare(e1.p, e2.p) >= 0 ? e1 : e2) 82 | .Select(e => e.v); 83 | } 84 | 85 | ///Returns the first item in a sequence, or else NoValue if the sequence is empty. 86 | [Pure] 87 | public static May MayFirst(this IEnumerable sequence) { 88 | if (sequence == null) throw new ArgumentNullException("sequence"); 89 | using (var e = sequence.GetEnumerator()) 90 | if (e.MoveNext()) 91 | return e.Current; 92 | return May.NoValue; 93 | } 94 | 95 | ///Returns the last item in a sequence, or else NoValue if the sequence is empty. 96 | [Pure] 97 | public static May MayLast(this IEnumerable sequence) { 98 | if (sequence == null) throw new ArgumentNullException("sequence"); 99 | 100 | // try to skip to the last item without enumerating when possible 101 | var list = sequence as IList; 102 | if (list != null) { 103 | if (list.Count == 0) return May.NoValue; 104 | return list[list.Count - 1]; 105 | } 106 | 107 | return sequence.MayAggregate((e1, e2) => e2); 108 | } 109 | 110 | ///Returns the single item in a sequence, NoValue if the sequence is empty, or throws an exception if there is more than one item. 111 | [Pure] 112 | public static May MaySingle(this IEnumerable sequence) { 113 | if (sequence == null) throw new ArgumentNullException("sequence"); 114 | 115 | using (var e = sequence.GetEnumerator()) { 116 | if (!e.MoveNext()) return May.NoValue; 117 | var result = e.Current; 118 | //note: this case is an exception to match the semantics of SingleOrDefault, not because it's the best approach 119 | if (e.MoveNext()) throw new ArgumentOutOfRangeException("sequence", "Expected either no items or a single item."); 120 | return result; 121 | } 122 | } 123 | 124 | /// 125 | /// Enumerates the values in the potential values in the sequence. 126 | /// The potential values that contain no value are skipped. 127 | /// 128 | [Pure] 129 | public static IEnumerable WhereHasValue(this IEnumerable> sequence) { 130 | return sequence.Where(e => e.HasValue).Select(e => (T)e); 131 | } 132 | 133 | /// 134 | /// Enumerates the values in all the potential values in the sequence. 135 | /// However, if any of the potential values contains no value then the entire result is no value. 136 | /// 137 | [Pure] 138 | public static May> MayAll(this IEnumerable> sequence) { 139 | if (sequence == null) throw new ArgumentNullException("sequence"); 140 | var result = new List(); 141 | foreach (var potentialValue in sequence) 142 | if (!potentialValue.IfHasValueThenDo(result.Add).HasValue) 143 | return May.NoValue; 144 | return result; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /MayExample/MayExample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D7365D22-E675-4319-8A9C-779497B852AD} 8 | WinExe 9 | Properties 10 | MayExample 11 | MayExample 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 1 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | False 28 | False 29 | True 30 | False 31 | False 32 | False 33 | True 34 | True 35 | True 36 | True 37 | True 38 | True 39 | True 40 | True 41 | False 42 | True 43 | False 44 | True 45 | False 46 | False 47 | False 48 | False 49 | True 50 | False 51 | True 52 | True 53 | True 54 | False 55 | False 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | True 64 | False 65 | False 66 | True 67 | Full 68 | DoNotBuild 69 | 0 70 | 71 | 72 | AnyCPU 73 | pdbonly 74 | true 75 | bin\Release\ 76 | TRACE 77 | prompt 78 | 4 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 4.0 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | MSBuild:Compile 99 | Designer 100 | 101 | 102 | MSBuild:Compile 103 | Designer 104 | 105 | 106 | App.xaml 107 | Code 108 | 109 | 110 | MainWindow.xaml 111 | Code 112 | 113 | 114 | 115 | 116 | 117 | Code 118 | 119 | 120 | True 121 | True 122 | Resources.resx 123 | 124 | 125 | True 126 | Settings.settings 127 | True 128 | 129 | 130 | ResXFileCodeGenerator 131 | Resources.Designer.cs 132 | 133 | 134 | SettingsSingleFileGenerator 135 | Settings.Designer.cs 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | {37468DFC-8378-4F4F-94F2-39F5C1AB8C5A} 145 | May 146 | 147 | 148 | 149 | 156 | -------------------------------------------------------------------------------- /MayTest/MayTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Strilanc.Value; 4 | using System.Linq; 5 | 6 | [TestClass] 7 | public class MayTest { 8 | [TestMethod] 9 | public void MayValue() { 10 | Assert.IsTrue(new May(1).HasValue); 11 | Assert.IsTrue(new May(1).ForceGetValue() == 1); 12 | Assert.IsTrue(new May("5").HasValue); 13 | Assert.IsTrue(new May("5").ForceGetValue() == "5"); 14 | 15 | Assert.IsTrue(!May.NoValue.HasValue); 16 | Assert.IsTrue(!May.NoValue.HasValue); 17 | Assert.IsTrue(!new May().HasValue); 18 | Assert.IsTrue(!new May().HasValue); 19 | Assert.IsTrue(!default(May).HasValue); 20 | Assert.IsTrue(!default(May).HasValue); 21 | 22 | TestUtil.AssertThrows(() => new May().ForceGetValue()); 23 | TestUtil.AssertThrows(() => new May().ForceGetValue()); 24 | TestUtil.AssertThrows(() => default(May).ForceGetValue()); 25 | TestUtil.AssertThrows(() => default(May).ForceGetValue()); 26 | } 27 | [TestMethod] 28 | public void MayMatch() { 29 | May.NoValue.Match(e => -e, () => 2).AssertEquals(2); 30 | May.NoValue.Match(e => -e, 2).AssertEquals(2); 31 | 1.Maybe().Match(e => -e, () => 2).AssertEquals(-1); 32 | 1.Maybe().Match(e => -e, 2).AssertEquals(-1); 33 | } 34 | [TestMethod] 35 | public void MayEquals() { 36 | // --- all forms of no value are equivalent 37 | // typed == operators 38 | Assert.IsTrue(May.NoValue == May.NoValue); 39 | Assert.IsTrue(May.NoValue == May.NoValue); 40 | Assert.IsTrue(May.NoValue == May.NoValue); 41 | Assert.IsTrue(May.NoValue == May.NoValue); 42 | Assert.IsTrue(May.NoValue == default(May)); 43 | Assert.IsTrue(May.NoValue == new May()); 44 | Assert.IsFalse(May.NoValue != May.NoValue); 45 | Assert.IsFalse(May.NoValue != May.NoValue); 46 | Assert.IsFalse(May.NoValue != May.NoValue); 47 | Assert.IsFalse(May.NoValue != May.NoValue); 48 | Assert.IsFalse(May.NoValue != default(May)); 49 | Assert.IsFalse(May.NoValue != new May()); 50 | // untyped Equals methods 51 | var ee = new object[] { 52 | May.NoValue, 53 | May.NoValue, 54 | May.NoValue, 55 | new May(), 56 | default(May) 57 | }; 58 | foreach (var e1 in ee) { 59 | foreach (var e2 in ee) { 60 | Assert.IsTrue(e1.Equals(e2)); 61 | Assert.IsTrue(e1.GetHashCode() == e2.GetHashCode()); 62 | Assert.IsTrue(((IMayHaveValue)e1).Equals((IMayHaveValue)e2)); 63 | } 64 | } 65 | 66 | // --- anything differing by has value or by value or by not being a May is not equivalent 67 | // same-typed == operators 68 | var mm = new[] { 69 | new May(1), 70 | new May(2), 71 | new May() 72 | }; 73 | for (var i = 0; i < mm.Length; i++) { 74 | for (var j = 0; j < mm.Length; j++) { 75 | Assert.IsTrue((mm[i] == mm[j]) == (i == j)); 76 | Assert.IsTrue((mm[i] != mm[j]) == (i != j)); 77 | } 78 | } 79 | // untyped Equals methods 80 | var oo = new object[] { 81 | May.NoValue, 82 | new May(1), 83 | new May(1), 84 | new May(2), 85 | new May("1"), 86 | new May("2"), 87 | "2", // unwrapped != wrapped in may 88 | null // null != NoValue 89 | }; 90 | for (var i = 0; i < oo.Length; i++) { 91 | for (var j = 0; j < oo.Length; j++) { 92 | Assert.IsTrue((Equals(oo[i], oo[j])) == (i == j)); 93 | } 94 | } 95 | } 96 | [TestMethod] 97 | public void ValueCasts() { 98 | ((int)1.Maybe()).AssertEquals(1); 99 | ((May)2).AssertEquals(new May(2)); 100 | TestUtil.AssertThrows(() => (int)May.NoValue); 101 | } 102 | [TestMethod] 103 | public void MayElse() { 104 | var n = May.NoValue; 105 | var y = new May(1); 106 | Func> throwsM = () => { throw new ArgumentException(); }; 107 | Func throwsT = () => { throw new ArgumentException(); }; 108 | 109 | // else T 110 | Assert.IsTrue(n.Else(2) == 2); 111 | Assert.IsTrue(y.Else(2) == 1); 112 | 113 | // else Func 114 | Assert.IsTrue(n.Else(() => 2) == 2); 115 | Assert.IsTrue(y.Else(() => 2) == 1); 116 | Assert.IsTrue(y.Else(throwsT) == y); 117 | TestUtil.AssertThrows(() => n.Else(throwsT)); 118 | 119 | // else May 120 | Assert.IsTrue(n.Else(y) == y); 121 | Assert.IsTrue(y.Else(n) == y); 122 | Assert.IsTrue(n.Else(2.Maybe()) == 2.Maybe()); 123 | Assert.IsTrue(y.Else(2.Maybe()) == y); 124 | Assert.IsTrue(n.Else(May.NoValue) == May.NoValue); 125 | Assert.IsTrue(y.Else(May.NoValue) == y); 126 | 127 | // else Func> 128 | Assert.IsTrue(n.Else(() => y) == y); 129 | Assert.IsTrue(y.Else(() => n) == y); 130 | Assert.IsTrue(n.Else(() => 2.Maybe()) == 2.Maybe()); 131 | Assert.IsTrue(y.Else(() => 2.Maybe()) == y); 132 | Assert.IsTrue(n.Else(() => new May()) == May.NoValue); 133 | Assert.IsTrue(y.Else(() => new May()) == y); 134 | Assert.IsTrue(y.Else(throwsM) == y); 135 | TestUtil.AssertThrows(() => n.Else(throwsM)); 136 | 137 | // else default 138 | Assert.IsTrue(n.ElseDefault() == 0); 139 | Assert.IsTrue(y.ElseDefault() == 1); 140 | 141 | // else action 142 | var x = 0; 143 | y.ElseDo(() => { x += 1; }); 144 | Assert.IsTrue(x == 0); 145 | n.ElseDo(() => { x += 1; }); 146 | Assert.IsTrue(x == 1); 147 | } 148 | [TestMethod] 149 | public void MayIfThenDo() { 150 | var i = 0; 151 | 1.Maybe().IfHasValueThenDo(e => { i += e + 1; }).HasValue.AssertIsTrue(); 152 | i.AssertEquals(2); 153 | May.NoValue.IfHasValueThenDo(e => { i += e + 1; }).HasValue.AssertIsFalse(); 154 | i.AssertEquals(2); 155 | } 156 | [TestMethod] 157 | public void Maybe() { 158 | 1.Maybe().AssertEquals(new May(1)); 159 | "5".Maybe().AssertEquals(new May("5")); 160 | ((string)null).Maybe().AssertEquals(new May(null)); 161 | } 162 | [TestMethod] 163 | public void MayUnwrap() { 164 | 1.Maybe().Maybe().Unwrap().AssertEquals(1.Maybe()); 165 | new May().Maybe().Unwrap().AssertEquals(May.NoValue); 166 | new May>().Unwrap().AssertEquals(May.NoValue); 167 | } 168 | [TestMethod] 169 | public void MaySelect() { 170 | 1.Maybe().Select(e => e + 1).AssertEquals(2.Maybe()); 171 | May.NoValue.Select(e => e + 2).AssertEquals(May.NoValue); 172 | } 173 | [TestMethod] 174 | public void MayBind() { 175 | 1.Maybe().Bind(e => 1.Maybe()).AssertEquals(1.Maybe()); 176 | 1.Maybe().Maybe().Bind(e => e).AssertEquals(1.Maybe()); 177 | May.NoValue.Bind(e => e.Maybe()).AssertEquals(May.NoValue); 178 | } 179 | [TestMethod] 180 | public void MaySelectMany() { 181 | (from e1 in 1.Maybe() from e2 in 2.Maybe() select e1 + e2).AssertEquals(3.Maybe()); 182 | (from e1 in new May() from e2 in 2.Maybe() select e1 + e2).AssertEquals(May.NoValue); 183 | (from e1 in new May() from e2 in new May() select e1 + e2).AssertEquals(May.NoValue); 184 | (from e1 in 1.Maybe() from e2 in new May() select e1 + e2).AssertEquals(May.NoValue); 185 | } 186 | [TestMethod] 187 | public void MayWhere() { 188 | Assert.IsTrue(1.Maybe().Where(e => e == 1) == 1.Maybe()); 189 | Assert.IsTrue(2.Maybe().Where(e => e == 1) == May.NoValue); 190 | Assert.IsTrue(new May().Where(e => e == 1) == May.NoValue); 191 | } 192 | [TestMethod] 193 | public void MayCombine() { 194 | var r = Enumerable.Range(0, 10).Select(e => e.Maybe()).ToArray(); 195 | 196 | // all present 197 | r[0].Combine(r[1], (e0, e1) => e0 + e1).AssertEquals(1.Maybe()); 198 | r[0].Combine(r[1], r[2], (e0, e1, e2) => e0 + e1 + e2).AssertEquals(3.Maybe()); 199 | r[0].Combine(r[1], r[2], r[3], (e0, e1, e2, e3) => e0 + e1 + e2 + e3).AssertEquals(6.Maybe()); 200 | //r[0].Combine(r[1], r[2], r[3], r[4], (e0, e1, e2, e3, e4) => e0 + e1 + e2 + e3 + e4).AssertEquals(10.Maybe()); 201 | 202 | // one missing 203 | for (var i = 0; i < r.Length; i++) { 204 | var s = r.ToArray(); 205 | s[i] = May.NoValue; 206 | if (i < 2) s[0].Combine(s[1], (e0, e1) => e0 + e1).AssertEquals(May.NoValue); 207 | if (i < 3) s[0].Combine(s[1], s[2], (e0, e1, e2) => e0 + e1 + e2).AssertEquals(May.NoValue); 208 | if (i < 4) s[0].Combine(s[1], s[2], s[3], (e0, e1, e2, e3) => e0 + e1 + e2 + e3).AssertEquals(May.NoValue); 209 | //if (i < 5) s[0].Combine(s[1], s[2], s[3], s[4], (e0, e1, e2, e3, e4) => e0 + e1 + e2 + e3 + e4).AssertEquals(May.NoValue); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /May/MayExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | 4 | namespace Strilanc.Value { 5 | ///Utility methods for the generic May type. 6 | public static class May { 7 | /// 8 | ///A potential value containing no value. Implicitely converts to a no value of any generic May type. 9 | ///Note: All forms of no value are equal, including May.NoValue, May<T>.NoValue, May<AnyOtherT>.NoValue, default(May<T>) and new May<T>(). 10 | ///Note: Null is NOT equivalent to new May<object>(null) and neither is equivalent to new May<string>(null). 11 | /// 12 | [Pure] 13 | public static MayNoValue NoValue { get { return default(MayNoValue); } } 14 | 15 | ///Returns a potential value containing the given value. 16 | [Pure] 17 | public static May Maybe(this T value) { 18 | return new May(value); 19 | } 20 | ///Matches this potential value either into a function expecting a value or against an alternative value. 21 | [Pure] 22 | public static TOut Match(this May potentialValue, Func valueProjection, TOut alternative) { 23 | if (valueProjection == null) throw new ArgumentNullException("valueProjection"); 24 | return potentialValue.Match(valueProjection, () => alternative); 25 | } 26 | ///Returns the potential result of potentially applying the given function to this potential value. 27 | [Pure] 28 | public static May Bind(this May potentialValue, Func> projection) { 29 | if (projection == null) throw new ArgumentNullException("projection"); 30 | return potentialValue.Match(projection, () => NoValue); 31 | } 32 | ///Returns the value contained in the given potential value, if any, or else the result of evaluating the given alternative value function. 33 | [Pure] 34 | public static T Else(this May potentialValue, Func alternativeFunc) { 35 | if (alternativeFunc == null) throw new ArgumentNullException("alternativeFunc"); 36 | return potentialValue.Match(e => e, alternativeFunc); 37 | } 38 | ///Flattens a doubly-potential value, with the result containing a value only if both levels contained a value. 39 | [Pure] 40 | public static May Unwrap(this May> potentialValue) { 41 | return potentialValue.Bind(e => e); 42 | } 43 | ///Returns the value contained in the given potential value, if any, or else the result of evaluating the given alternative potential value function. 44 | [Pure] 45 | public static May Else(this May potentialValue, Func> alternative) { 46 | if (alternative == null) throw new ArgumentNullException("alternative"); 47 | return potentialValue.Match(e => e.Maybe(), alternative); 48 | } 49 | ///Returns the value contained in the given potential value, if any, or else the given alternative value. 50 | [Pure] 51 | public static T Else(this May potentialValue, T alternative) { 52 | return potentialValue.Else(() => alternative); 53 | } 54 | ///Returns the value contained in the given potential value, if any, or else the given alternative potential value. 55 | [Pure] 56 | public static May Else(this May potentialValue, May alternative) { 57 | return potentialValue.Else(() => alternative); 58 | } 59 | ///Returns the result of potentially applying a function to this potential value. 60 | [Pure] 61 | public static May Select(this May value, Func projection) { 62 | if (projection == null) throw new ArgumentNullException("projection"); 63 | return value.Bind(e => projection(e).Maybe()); 64 | } 65 | ///Returns the same value, unless the contained value does not match the filter in which case a no value is returned. 66 | [Pure] 67 | public static May Where(this May value, Func filter) { 68 | if (filter == null) throw new ArgumentNullException("filter"); 69 | return value.Bind(e => filter(e) ? e.Maybe() : NoValue); 70 | } 71 | ///Projects optional values, returning a no value if anything along the way is a no value. 72 | [Pure] 73 | public static May SelectMany(this May source, 74 | Func> maySelector, 75 | Func resultSelector) { 76 | if (maySelector == null) throw new ArgumentNullException("maySelector"); 77 | if (resultSelector == null) throw new ArgumentNullException("resultSelector"); 78 | return source.Bind(s => maySelector(s).Select(m => resultSelector(s, m))); 79 | } 80 | ///Combines the values contained in several potential values with a projection function, returning no value if any of the inputs contain no value. 81 | [Pure] 82 | public static May Combine(this May potentialValue1, 83 | May potentialValue2, 84 | Func resultSelector) { 85 | if (resultSelector == null) throw new ArgumentNullException("resultSelector"); 86 | return from v1 in potentialValue1 87 | from v2 in potentialValue2 88 | select resultSelector(v1, v2); 89 | } 90 | ///Combines the values contained in several potential values with a projection function, returning no value if any of the inputs contain no value. 91 | [Pure] 92 | public static May Combine(this May potentialValue1, 93 | May potentialValue2, 94 | May potentialValue3, 95 | Func resultSelector) { 96 | if (resultSelector == null) throw new ArgumentNullException("resultSelector"); 97 | return from v1 in potentialValue1 98 | from v2 in potentialValue2 99 | from v3 in potentialValue3 100 | select resultSelector(v1, v2, v3); 101 | } 102 | ///Combines the values contained in several potential values with a projection function, returning no value if any of the inputs contain no value. 103 | [Pure] 104 | public static May Combine(this May potentialValue1, 105 | May potentialValue2, 106 | May potentialValue3, 107 | May potentialValue4, 108 | Func resultSelector) { 109 | if (resultSelector == null) throw new ArgumentNullException("resultSelector"); 110 | return from v1 in potentialValue1 111 | from v2 in potentialValue2 112 | from v3 in potentialValue3 113 | from v4 in potentialValue4 114 | select resultSelector(v1, v2, v3, v4); 115 | } 116 | /// 117 | /// Potentially runs an action taking the potential value's value. 118 | /// No effect if the potential value is no value. 119 | /// Returns an IMayHaveValue that has a value iff the action was run. 120 | /// 121 | [Pure] 122 | public static IMayHaveValue IfHasValueThenDo(this May potentialValue, Action hasValueAction) { 123 | if (hasValueAction == null) throw new ArgumentNullException("hasValueAction"); 124 | return potentialValue.Select(e => { 125 | hasValueAction(e); 126 | return 0; 127 | }); 128 | } 129 | ///Runs the given no value action if the given potential value does not contain a value, and otherwise does nothing. 130 | [Pure] 131 | public static void ElseDo(this IMayHaveValue potentialValue, Action noValueAction) { 132 | if (potentialValue == null) throw new ArgumentNullException("potentialValue"); 133 | if (noValueAction == null) throw new ArgumentNullException("noValueAction"); 134 | if (!potentialValue.HasValue) noValueAction(); 135 | } 136 | ///Returns the value contained in the given potential value, if any, or else the type's default value. 137 | [Pure] 138 | public static T ElseDefault(this May potentialValue) { 139 | return potentialValue.Else(default(T)); 140 | } 141 | 142 | ///Returns the value contained in the potential value, or throws an InvalidOperationException if it contains no value. 143 | [Pure] 144 | public static T ForceGetValue(this May potentialValue) { 145 | return potentialValue.Match( 146 | e => e, 147 | () => { throw new InvalidOperationException("No Value"); }); 148 | } 149 | } 150 | } 151 | --------------------------------------------------------------------------------