Skip to content

Commit 39f0128

Browse files
authored
C# implementation of Transactions for Procedures (#3809)
# Description of Changes Implements the C# equivalent of #3638 This implement uses inheritance, where abstract base classes (like `ProcedureContextBase` in `ProcedureContext.cs`) store the core of the implementation, and then generated wrappers (like `ProcedureContext` in the generated FFI.cs file) inherit from them. For error handling, we work like Rust's implementation of `Result<T,E>` but we require `where E : Exception` because of how exceptions work in C#. Transaction-level failures come back as a `TxOutcome` and user errors should follow the `Result<T,E>` pattern. In this implementation, we have `UnwrapOrThrow()` throws exceptions directly because of C#'s error handling pattern. Unlike the Rust implementation's direct `Result` propagation, we are using an `AbortGuard` pattern (in `ProcedureContext.cs`) for exception handling, which uses `IDisposable` for automatic cleanup. Most changes should have fairly similar Rust-equivalents beyond that. For module authors, the changes here allow for the transation logic to work like: ```csharp ctx.TryWithTx<ResultType, Exception>(tx => { // transaction logic return Result<ResultType, Exception>.Ok(result); }); ``` This change includes a number of tests added to the `sdks/csharp/examples~/regression-tests/`'s `server` and `client` to validate the behavior of the changes. `server` changes provide further usage examples for module authors. # API and ABI breaking changes Should not be a breaking change # Expected complexity level and risk 2 # Testing - [x] Created Regression Tests that show transitions in procedures working in various ways, all of which pass.
1 parent c38b135 commit 39f0128

38 files changed

Lines changed: 3997 additions & 1997 deletions

crates/bindings-csharp/BSATN.Codegen/Type.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public abstract record TypeUse(string Name, string BSATNName)
5353
/// <returns></returns>
5454
public static TypeUse Parse(ISymbol member, ITypeSymbol typeSymbol, DiagReporter diag)
5555
{
56+
if (typeSymbol.SpecialType == SpecialType.System_Void)
57+
{
58+
// Treat void as equivalent to Unit type
59+
return new ReferenceUse("SpacetimeDB.Unit", "SpacetimeDB.Unit.BSATN");
60+
}
61+
5662
var type = SymbolToName(typeSymbol);
5763
string typeInfo;
5864

crates/bindings-csharp/BSATN.Runtime.Tests/BSATN.Runtime.Tests.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
43
<IsPackable>false</IsPackable>
54
<IsTestProject>true</IsTestProject>
@@ -20,7 +19,10 @@
2019

2120
<ItemGroup>
2221
<ProjectReference Include="../BSATN.Runtime/BSATN.Runtime.csproj" />
23-
<ProjectReference Include="../BSATN.Codegen/BSATN.Codegen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
22+
<ProjectReference
23+
Include="../BSATN.Codegen/BSATN.Codegen.csproj"
24+
OutputItemType="Analyzer"
25+
ReferenceOutputAssembly="false"
26+
/>
2427
</ItemGroup>
25-
2628
</Project>

crates/bindings-csharp/BSATN.Runtime.Tests/Tests.cs

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -246,20 +246,12 @@ public BasicDataClass((int x, string y, int? z, string? w) data)
246246
}
247247

248248
[Type]
249-
public partial struct BasicDataStruct
249+
public partial struct BasicDataStruct((int x, string y, int? z, string? w) data)
250250
{
251-
public int X;
252-
public string Y;
253-
public int? Z;
254-
public string? W;
255-
256-
public BasicDataStruct((int x, string y, int? z, string? w) data)
257-
{
258-
X = data.x;
259-
Y = data.y;
260-
Z = data.z;
261-
W = data.w;
262-
}
251+
public int X = data.x;
252+
public string Y = data.y;
253+
public int? Z = data.z;
254+
public string? W = data.w;
263255
}
264256

265257
[Type]
@@ -315,12 +307,12 @@ public void Add(bool collides)
315307
}
316308
}
317309

318-
public double CollisionFraction
310+
public readonly double CollisionFraction
319311
{
320312
get => (double)Collisions / (double)Comparisons;
321313
}
322314

323-
public void AssertCollisionsLessThan(double fraction)
315+
public readonly void AssertCollisionsLessThan(double fraction)
324316
{
325317
Assert.True(
326318
CollisionFraction < fraction,
@@ -626,18 +618,13 @@ public static void GeneratedNestedListRoundTrip()
626618
.Select(list => new ContainsNestedList(list));
627619
#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
628620

629-
630621
static readonly Gen<(ContainsNestedList e1, ContainsNestedList e2)> GenTwoContainsNestedList =
631622
Gen.Select(GenContainsNestedList, GenContainsNestedList, (e1, e2) => (e1, e2));
632623

633-
class EnumerableEqualityComparer<T> : EqualityComparer<IEnumerable<T>>
624+
class EnumerableEqualityComparer<T>(EqualityComparer<T> equalityComparer)
625+
: EqualityComparer<IEnumerable<T>>
634626
{
635-
private readonly EqualityComparer<T> EqualityComparer;
636-
637-
public EnumerableEqualityComparer(EqualityComparer<T> equalityComparer)
638-
{
639-
EqualityComparer = equalityComparer;
640-
}
627+
private readonly EqualityComparer<T> EqualityComparer = equalityComparer;
641628

642629
public override bool Equals(IEnumerable<T>? x, IEnumerable<T>? y) =>
643630
x == null ? y == null : (y == null ? false : x.SequenceEqual(y, EqualityComparer));

crates/bindings-csharp/BSATN.Runtime/BSATN.Runtime.csproj

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
43
<AssemblyName>SpacetimeDB.BSATN.Runtime</AssemblyName>
54
<Version>1.11.1</Version>
@@ -18,18 +17,25 @@
1817

1918
<ItemGroup>
2019
<!-- We want to build BSATN.Codegen both to include it in our NuGet package but also we want it to transform [SpacetimeDB.Type] usages in BSATN.Runtime code itself. -->
21-
<ProjectReference Include="../BSATN.Codegen/BSATN.Codegen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
20+
<ProjectReference
21+
Include="../BSATN.Codegen/BSATN.Codegen.csproj"
22+
OutputItemType="Analyzer"
23+
ReferenceOutputAssembly="false"
24+
/>
2225
</ItemGroup>
2326

2427
<ItemGroup>
2528
<None Include="../Runtime/README.md" Pack="true" PackagePath="" />
2629
<!-- We want all users who depends on BSATN.Runtime to automatically get the Roslyn codegen component as well. -->
27-
<None Include="../BSATN.Codegen/bin/$(Configuration)/netstandard2.0/SpacetimeDB.BSATN.Codegen.dll" Pack="true" PackagePath="analyzers/dotnet/cs" />
30+
<None
31+
Include="../BSATN.Codegen/bin/$(Configuration)/netstandard2.0/SpacetimeDB.BSATN.Codegen.dll"
32+
Pack="true"
33+
PackagePath="analyzers/dotnet/cs"
34+
/>
2835
</ItemGroup>
2936

3037
<ItemGroup>
3138
<!-- dev-dependency to add internal C# classes and attributes not included in .NET Standard -->
3239
<PackageReference Include="PolySharp" Version="1.14.1" PrivateAssets="all" />
3340
</ItemGroup>
34-
3541
</Project>

crates/bindings-csharp/BSATN.Runtime/BSATN/AlgebraicType.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,11 @@ public interface ITypeRegistrar
66
}
77

88
[SpacetimeDB.Type]
9-
public partial struct AggregateElement
9+
public partial struct AggregateElement(string name, AlgebraicType algebraicType)
1010
{
11-
public string? Name;
11+
public string? Name = name;
1212

13-
public AlgebraicType AlgebraicType;
14-
15-
public AggregateElement(string name, AlgebraicType algebraicType)
16-
{
17-
Name = name;
18-
AlgebraicType = algebraicType;
19-
}
13+
public AlgebraicType AlgebraicType = algebraicType;
2014
}
2115

2216
[SpacetimeDB.Type]

0 commit comments

Comments
 (0)