Looking for the best way to use OneOf multiple times, returning early depending on the result, specifically if you can get an Error along the way.
For example, consider a builder where you need to build individual parts and then combine them. Each part builder returns a OneOf<ThisPart, Error>, so for every step along the way, we might need to return early with that error.
// Assume these have real implementations and can return errors:
OneOf<SteeringWheel, Error> BuildSteeringWheel() => new SteeringWheel();
OneOf<Engine, Error> BuildEngine() => new Engine();
OneOf<Stereo, Error> BuildStereo() => new Stereo();
public record class Car(SteeringWheel SteeringWheel, Engine Engine, Stereo Stereo);
public record class SteeringWheel();
public record class Engine();
public record class Stereo();
To build a car, I need each of the three components, and return early if any component builder gives me an error. I can think of a few ways, but nothing feels right:
// This one is ugly and would really get nasty the more complexity you add
OneOf<Car, Error> BuildCar_Nested()
{
return BuildSteeringWheel().Match(
steeringWheel => BuildEngine().Match(
engine => BuildStereo().Match<OneOf<Car, Error>>(
stereo => new Car(steeringWheel, engine, stereo),
stereoError => stereoError
),
engineError => engineError
),
steeringWheelError => steeringWheelError
);
}
// The AsT1 property feels like a cheat, and pretty much provides the same use
// as if I would have returned a (T, Error) tuple, which kills the point
// of using OneOf. Same would be true if I used the TryPickT0 methods
OneOf<Car, Error> BuildCar_AsT()
{
OneOf<SteeringWheel, Error> steeringWheel = BuildSteeringWheel();
if (steeringWheel.IsT1)
{
return steeringWheel.AsT1;
}
OneOf<Engine, Error> engine = BuildEngine();
if (engine.IsT1)
{
return engine.AsT1;
}
OneOf<Stereo, Error> stereo = BuildStereo();
if (stereo.IsT1)
{
return stereo.AsT1;
}
return new Car(steeringWheel.AsT0, engine.AsT0, stereo.AsT0);
}
// This forces me to deal with all result types by using Switch, but it
// also makes me check the error each time, and do redundant work by null-checking
// something that is definitely not null, or some other workaround. Again this
// pattern is pretty much no different from returning a Tuple of (T, Error).
// Also trying not to use the ! operator if possible.
OneOf<Car, Error> BuildCar_Switches()
{
Error? error = null;
SteeringWheel? steeringWheel = null;
BuildSteeringWheel().Switch(
sw => steeringWheel = sw,
err => error = err
);
if (error is not null) { return error.Value; }
// This avoids compiler warnings
// Or I could have done SteeringWheel steeringWheel = null!; above
if (steeringWheel is null) { return new Error(); }
Engine? engine = null;
BuildEngine().Switch(
e => engine = e,
err => error = err
);
if (error is not null) { return error.Value; }
if (engine is null) { return new Error(); }
Stereo? stereo = null;
BuildStereo().Switch(
s => stereo = s,
err => error = err
);
if (error is not null) { return error.Value; }
if (stereo is null) { return new Error(); }
return new Car(steeringWheel, engine, stereo);
}
Is there a clean and proper way of doing this? The Switch approach seems to be the least incorrect and the approach I'll take if I have to, but I'm still looking for a better way.