Skip to content

Commit d535d88

Browse files
authored
Merge pull request #6 from ehonda/feature/improve-readme
Add section about implicit conversions to `README.md`
2 parents dbf4018 + 7bb54ef commit d535d88

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Option<string> none = Option.None<string>();
2222
Option<string> implicitSome = "hello";
2323
Option<string> defaultNone = default;
2424

25+
// Creation from interfaces
26+
IService service = new Service();
27+
Option<IService> interfaceSome = Option.Some(service); // ✅ Use Some for interfaces
28+
// Option<IService> interfaceImplicit = service; // ❌ Does not compile (see Limitations below)
29+
2530
// Check state
2631
if (some.HasValue)
2732
{
@@ -62,3 +67,41 @@ IService CreateService(Option<IDependency> dependency = default)
6267
CreateService(); // dependency is None -> uses CreateDependency()
6368
CreateService(null); // dependency is Some(null) -> uses null
6469
```
70+
71+
## Implicit Conversions
72+
73+
`Option<T>` supports implicit conversions from `T` to `Option<T>`, enabling natural syntax:
74+
75+
```csharp
76+
Option<int> some = 42; // Some(42)
77+
Option<string?> someNull = null; // Some(null)
78+
```
79+
80+
### ⚠️ Limitations with Interfaces
81+
82+
You will encounter a compiler error when implicitly converting an interface variable to `Option<Interface>`.
83+
84+
```csharp
85+
public interface IService { }
86+
public class Service : IService { }
87+
88+
Service service = new Service();
89+
Option<IService> works = service; // ✅ Works
90+
91+
IService interfaceRef = new Service();
92+
Option<IService> fails = interfaceRef; // ❌ Compiler Error
93+
```
94+
95+
This is due to how the C# compiler resolves [User-defined implicit conversions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#1054-user-defined-implicit-conversions) (Section 10.5.4).
96+
97+
The compiler looks for conversion operators that convert from a type **encompassing** the source type. However, the definition of "encompassing" in [Evaluation of user-defined conversions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#1053-evaluation-of-user-defined-conversions) (Section 10.5.3) explicitly excludes interfaces:
98+
99+
> "If a standard implicit conversion ... exists from a type A to a type B, and if **neither A nor B are interface_types**, then A is said to be encompassed by B"
100+
101+
Since `IService` is an interface, it is not considered to be encompassed by the operator's parameter type, so the conversion is not found.
102+
103+
**Workaround:** Use `Option.Some()` explicitly:
104+
105+
```csharp
106+
Option<IService> fixed = Option.Some(interfaceRef);
107+
```

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<PackageReadmeFile>README.md</PackageReadmeFile>
1313
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1414
<PackageTags>optional option maybe</PackageTags>
15-
<Version>2.0.0</Version> <!-- Common version for all packages in src -->
15+
<Version>2.1.0</Version> <!-- Common version for all packages in src -->
1616
<PackageReleaseNotes>See package release notes on GitHub: https://github.com/ehonda/Optional/releases/tag/$(PackageId)-v$(Version)</PackageReleaseNotes>
1717
<DefineConstants>JETBRAINS_ANNOTATIONS</DefineConstants>
1818

tests/Core.Tests/OptionTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,27 @@ public async Task Implicit_Conversion_From_Value_Should_Create_Some()
103103
await Assert.That(option.HasValue).IsTrue();
104104
await Assert.That(option.Value).IsEqualTo(42);
105105
}
106+
107+
private interface IService
108+
{
109+
int GetValue();
110+
}
111+
112+
private class Service : IService
113+
{
114+
public int GetValue() => 42;
115+
}
116+
117+
[Test]
118+
public async Task Implicit_Conversion_From_Class_To_Interface_Should_Create_Some()
119+
{
120+
// Implicit conversion from IService does not work, which is why we don't test for it.
121+
// See limitations in README.md
122+
Option<IService> option = new Service();
123+
124+
await Assert.That(option.HasValue).IsTrue();
125+
await Assert.That(option.Value!.GetValue()).IsEqualTo(42);
126+
}
106127

107128
[Test]
108129
public async Task Implicit_Conversion_From_Null_Should_Create_Some_Null()

0 commit comments

Comments
 (0)