{"id":27646078,"url":"https://github.com/ntwilson/result-dotnet","last_synced_at":"2025-04-24T01:16:32.637Z","repository":{"id":133406995,"uuid":"80969417","full_name":"ntwilson/result-dotnet","owner":"ntwilson","description":"Result class for FP-style error handling in C#","archived":false,"fork":false,"pushed_at":"2019-05-22T09:50:53.000Z","size":60,"stargazers_count":4,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-08T07:51:18.520Z","etag":null,"topics":["csharp","error-handling","functional","functional-programming","result"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ntwilson.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-05T04:49:02.000Z","updated_at":"2023-11-30T10:10:27.000Z","dependencies_parsed_at":null,"dependency_job_id":"a74ca333-85cd-41db-820b-9e45f121affc","html_url":"https://github.com/ntwilson/result-dotnet","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntwilson%2Fresult-dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntwilson%2Fresult-dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntwilson%2Fresult-dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ntwilson%2Fresult-dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ntwilson","download_url":"https://codeload.github.com/ntwilson/result-dotnet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250540936,"owners_count":21447428,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","error-handling","functional","functional-programming","result"],"created_at":"2025-04-24T01:16:32.060Z","updated_at":"2025-04-24T01:16:32.623Z","avatar_url":"https://github.com/ntwilson.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.com/ntwilson/result-dotnet.svg?branch=master)](https://travis-ci.com/ntwilson/result-dotnet)\n\n# ResultDotNet\nThis library adds a Result class for FP-style error handling in C#.\n\n## namespace overview\nThere are two classes in the ResultDotNet namespace: a `Result\u003ctVal, tErr\u003e` generic data type, and a `Result` static class.  The former is a data type that can be used to model values that might come back as an error, along with members to consume that data.  The latter is a set of static methods that work on the Result data type that don't read well as members - notably Map2 through Map4 and Bind2 through Bind4 - as well as functions for creating new Result data types.\n\n## usage\n### from C\u0026#35;\n#### creating a Result\u003ctVal, tErr\u003e\n```C#\nusing ResultDotNet;\n...\nResult\u003cdouble, string\u003e divide(double numerator, double denominator) =\u003e\n  (denominator == 0)\n    ? Result.Error\u003cdouble, string\u003e(\"Cannot divide by 0!\")\n    : Result.Ok\u003cdouble, string\u003e(numerator / denominator);\n```\nYou could also use the C#6 `using static` feature to simplify the above to:\n```C#\nusing ResultDotNet;\nusing static ResultDotNet.Result;\n...\nResult\u003cdouble, string\u003e divide(double numerator, double denominator) =\u003e\n  (denominator == 0)\n    ? Error\u003cdouble, string\u003e(\"Cannot divide by 0!\")\n    : Ok\u003cdouble, string\u003e(numerator / denominator);\n```\n\n#### extracting the value of a Result\u003ctVal, tErr\u003e\nThe easiest way to get the result is to use the `Match()` member:\n```C#\nusing ResultDotNet;\n...\nstring pricePerUnitForDisplay(Invoice invoice) =\u003e\n  divide(invoice.Total, invoice.NumberOfUnits).Match(\n    ok: ppu =\u003e ppu.ToString(),\n    error: err =\u003e $\"N/A: {err}\");\n```\nSometimes you end up with a `Result\u003cTVal, TErr\u003e`, but you really just want the value, and the program should crash if the Result is an Error.  Now you could do this with a regular Match statement:\n\n```c#\nvar value = result.Match(\n  ok: val =\u003e val\n  err: { throw new ResultExpectedException(\"Something went wrong\"); });\n```\n\nBut we provide a method just for doing that more conveniently:\n\n```c#\nvar value = result.Unless(\"Something went wrong\");\n```\n\n(And if you track code coverage, you don't even need to assemble a test with the error condition to get full code coverage).\n\nWe also provide a method for cases where there's no need to provide an additional message, the error type speaks for itself:\n\n```C#\nvar session = tryLogin(username, password).Expect();\n```\n\n\n\nResult is a union of types `Result\u003ctVal, tErr\u003e.Ok` and `Result\u003ctVal, tErr\u003e.Error` (`Result\u003ctVal,tErr\u003e` itself is abstract, and has the two unioned types as concrete child classes), so you can also manually check the types:\n\n```C#\nusing ResultDotNet;\n...\nstring pricePerUnitForDisplay(Invoice invoice) {\n  var ans = divide(invoice.Total, invoice.NumberOfUnits);\n  if (ans is Result\u003cdouble, string\u003e.Ok) \n    return (ans as Result\u003cdouble, string\u003e.Ok).Item.ToString();\n  else {\n    var err = (ans as Result\u003cdouble, string\u003e.Error).Item;\n    return $\"N/A: {err}\";\n  }\n}\n```\nFollowing the same idea, you could use pattern matching from C# 7, to write something like:\n```C#\nusing ResultDotNet;\n...\nstring pricePerUnitForDisplay(Invoice invoice) =\u003e\n  var ans = divide(invoice.Total, invoice.NumberOfUnits);\n  if (ans is Result\u003cdouble,string\u003e.Ok div) \n    return div.Item.ToString();\n  else if (ans is Result\u003cdouble,string\u003e.Error err)\n    return $\"N/A: {err.Item}\";\n  else ...\n```\n\n\n\n#### map and bind as members\n\n(I apologize for the *totally* contrived examples)\n```C#\nusing ResultDotNet;\n...\nResult\u003cdouble, string\u003e pricePerUnit(Invoice invoice) =\u003e divide(invoice.Total, invoice.NumberOfUnits);\nResult\u003cdouble, string\u003e savingsPerUnit(Invoice invoice, double dollarsOff) =\u003e\n  pricePerUnit(invoice).Bind(ppu =\u003e divide(dollarsOff, ppu));\n\nResult\u003cdouble, string\u003e pricePerUnitWithDiscount(Invoice invoice, double dollarsOffPerUnit) =\u003e\n  pricePerUnit(invoice).Map(ppu =\u003e ppu - dollarsOffPerUnit);\n```\nYou can also use LINQ to build expressions using Results.  You can think of the Result a bit like a collection that contains the successful result when assembling a LINQ expression. It can often be more intuitive and readable, at the expense of being slightly more total code:\n```C#\nusing ResultDotNet;\n...\nResult\u003cdouble, string\u003e pricePerUnit(Invoice invoice) =\u003e divide(invoice.Total, invoice.NumberOfUnits);\nResult\u003cdouble, string\u003e savingsPerUnit(Invoice invoice, double dollarsOff) =\u003e\n  from ppu in pricePerUnit(invoice) \n  from spu in divide(dollarsOff, ppu)\n  select spu;\n\nResult\u003cdouble, string\u003e pricePerUnitWithDiscount(Invoice invoice, double dollarsOffPerUnit) =\u003e\n  from ppu in pricePerUnit(invoice) select ppu - dollarsOffPerUnit;\n```\n\n#### map and bind as functions\nmap and bind themselves have static functions:\n```C#\nusing ResultDotNet;\n...\nResult\u003cdouble, string\u003e pricePerUnit(Invoice invoice) =\u003e divide(invoice.Total, invoice.NumberOfUnits);\nResult\u003cdouble, string\u003e savingsPerUnit(Invoice invoice, double dollarsOff) =\u003e\n  Result.Bind(ppu =\u003e divide(dollarsOff, ppu),  pricePerUnit(invoice));\n\nResult\u003cdouble, string\u003e pricePerUnitWithDiscount(Invoice invoice, double dollarsOffPerUnit) =\u003e\n  Result.Map(ppu =\u003e ppu - dollarsOffPerUnit, pricePerUnit(invoice));\n```\nbut there are also functions for Map2 through Map4 and Bind2 through Bind4 that only exist as static functions (object methods are hard to read when binding or mapping with multiple Results)\n```C#\nusing ResultDotNet;\n...\nInvoice createInvoice(double total, double numberOfUnits) =\u003e new Invoice(total, numberOfUnits);\n\nResult\u003cInvoice, string\u003e createInvoice(Result\u003cdouble, string\u003e total, Result\u003cdouble, string\u003e numberOfUnits) =\u003e\n  Result.Map2(createInvoice, total, numberOfUnits);\n```\n\n#### taking actions on ok or error\nif you need to take an action on ok or error instead of returning a value, you can use the overloads for the `Match()` member, or the `IfOk()` and `IfError()` members:\n```C#\nusing ResultDotNet;\n...\nResult\u003cDataTable, string\u003e result = executeDatabaseQuery(sql);\nresult.IfError(err =\u003e logger.Log(err));\n```\n\n```C#\nusing ResultDotNet;\n...\nResult\u003cDataTable, string\u003e result = executeDatabaseQuery(sql);\nresult.Match(\n  ok: val =\u003e logger.Log($\"DB query ran successfully: {sql}\"),\n  error: err =\u003e logger.Log($\"DB query FAILED: {sql}\"));\n```\n\n#### \n\n### from F\u0026#35;\n\nSince Result uses many higher order functions, using the C# interface doesn't interop well with F# (since F# prefers `FSharpFunc`s instead of `System.Func`s).\nTo make usage from F# easier, there's a ResultDotNet.FSharp namespace that shadows the Result module with one that uses F#-friendly functions \n#### creating a Result\u003c'tVal, 'tErr\u003e\n```F#\nopen ResultDotNet\n...\nlet divide (numerator:float) (denominator:float) =\n  if denominator = 0.\n  then Error \"Cannot divide by 0!\"\n  else Ok (numerator / denominator)\n```\n\n#### extracting the value of a Result\u003c'tVal, 'tErr\u003e\nResult is a union of types `Ok of 'tVal` and `Error of 'tErr`, so the easiest way to get the result is to use a `match` statement:\n```F#\nopen ResultDotNet\n...\nlet pricePerUnitForDisplay invoice =\n  match divide invoice.Total invoice.NumberOfUnits with\n  | Ok ppu -\u003e ppu.ToString()\n  | Error err -\u003e \"N/A: \" + err\n```\n\nSometimes you end up with a `Result\u003c'tVal, 'tErr\u003e`, but you really just want the value, and the program should crash if the Result is an Error.  Now you could do this with a regular Match statement:\n\n```c#\nlet value = \n  match result with\n  | Ok val -\u003e val\n  | Error err -\u003e raise (ResultExpectedException(\"Something went wrong\"))\n```\n\nBut we provide a function just for doing that more conveniently:\n\n```c#\nlet value = result |\u003e Result.unless(\"Something went wrong\");\n```\n\n(And if you track code coverage, you don't even need to assemble a test with the error condition to get full code coverage).\n\nWe also provide a function for cases where there's no need to provide an additional message, the error type speaks for itself:\n\n```F#\nlet session = tryLogin username password |\u003e Result.expect\n```\n\n\n\n#### map and bind as members\n\n(I apologize for the *totally* contrived examples)\n```F#\nopen ResultDotNet\nopen ResultDotNet.FSharp\n...\ntype Invoice = { Total:float; NumberOfUnits:float }\nlet pricePerUnit invoice = divide invoice.Total invoice.NumberOfUnits\nlet savingsPerUnit invoice dollarsOff =\n  pricePerUnit invoice |\u003e Result.bind (fun ppu -\u003e divide dollarsOff ppu)\n  // you could of course `pricePerUnit () |\u003e Result.bind (divide dollarsOff)`\n  // but I find it counterintuitive that dollarsOff would be the numerator with that syntax\n\nlet pricePerUnitWithDiscount invoice dollarsOffPerUnit =\n  pricePerUnit invoice |\u003e Result.map (fun ppu -\u003e ppu - dollarsOffPerUnit)\n```\n```F#\nopen ResultDotNet\nopen ResultDotNet.FSharp\n...\ntype Invoice = { Total:float; NumberOfUnits:float }\nlet newInvoice total numberOfUnits = { Total = total; NumberOfUnits = numberOfUnits }\n\nlet createInvoice (total:Result\u003cdouble, string\u003e) (numberOfUnits:Result\u003cdouble, string\u003e) =\n  Result.map2 newInvoice total numberOfUnits\n```\n\n#### taking actions on ok or error\nif you need to take an action on ok or error instead of returning a value, you can use the match statement as normal, or you can use the `ifOk` and `ifError` functions:\n```F#\nopen ResultDotNet\nopen ResultDotNet.FSharp\n...\nlet result:Result\u003cDataTable, string\u003e = executeDatabaseQuery sql\nresult |\u003e Result.ifError (logger.Log);\n```\n\n#### computation expressions\nwhen using ResultDotNet from F#, you can use a computation expression in place of bind \u0026 map.  These are repeats of the above examples now using computation expressions:\n```F#\nopen ResultDotNet\nopen ResultDotNet.FSharp\n...\ntype Invoice = { Total:float; NumberOfUnits:float }\nlet pricePerUnit invoice = divide invoice.Total invoice.NumberOfUnits\nlet savingsPerUnit invoice dollarsOff =\n  result {\n    let! ppu = pricePerUnit invoice\n    return! divide dollarsOff ppu\n  }\n\nlet pricePerUnitWithDiscount invoice dollarsOffPerUnit =\n  result {\n    let! ppu = pricePerUnit invoice\n    return ppu - dollarsOffPerUnit\n  }\n```\n```F#\nopen ResultDotNet\nopen ResultDotNet.FSharp\n...\ntype Invoice = { Total:float; NumberOfUnits:float }\nlet newInvoice total numberOfUnits = { Total = total; NumberOfUnits = numberOfUnits }\n\nlet createInvoice (total:Result\u003cdouble, string\u003e) (numberOfUnits:Result\u003cdouble, string\u003e) =\n  result {\n    let! t = total\n    let! n = numberOfUnits\n    return newInvoice t n\n  }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fntwilson%2Fresult-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fntwilson%2Fresult-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fntwilson%2Fresult-dotnet/lists"}