I'm using .NET 3.5. We have some complex third-party classes which are automatically generated and out of my control, but which we must work with for testing purposes. I see my team doing a lot of deeply-nested property getting/setting in our test code, and it's getting pretty cumbersome.
To remedy the problem, I'd like to make a fluent interface for setting properties on the various objects in the hierarchical tree. There are a large number of properties and classes in this third-party library, and it would be too tedious to map everything manually.
My initial thought was to just use object initializers. Red
, Blue
, and Green
are properties, and Mix()
is a method that sets a fourth property Color
to the closest RGB-safe color with that mixed color. Paints must be homogenized with Stir()
before they can be used.
Bucket b = new Bucket() {
Paint = new Paint() {
Red = 0.4;
Blue = 0.2;
Green = 0.1;
}
};
That works to initialize the Paint
, but I need to chain Mix()
and other methods to it. Next attempt:
Create<Bucket>(Create<Paint>()
.SetRed(0.4)
.SetBlue(0.2)
.SetGreen(0.1)
.Mix().Stir()
)
But that doesn't scale well, because I'd have to define a method for each property I want to set, and there are hundreds of different properties in all the classes. Also, C# doesn't have a way to dynamically define methods prior to C# 4, so I don't think I can hook into things to do this automatically in some way.
Third attempt:
Create<Bucket>(Create<Paint>().Set(p => {
p.Red = 0.4;
p.Blue = 0.2;
p.Green = 0.1;
}).Mix().Stir()
)
That doesn't look too bad, and seems like it'd be feasible. Is this an advisable approach? Is it possible to write a Set
method that works this way? Or should I be pursuing an alternate strategy?
Does this work?
Bucket b = new Bucket() {
Paint = new Paint() {
Red = 0.4;
Blue = 0.2;
Green = 0.1;
}.Mix().Stir()
};
Assuming Mix()
and Stir()
are defined to return a Paint
object.
To call methods that return void
, you can use an extension method that will allow you to perform additional initialization on the object you pass in:
public static T Init<T>(this T @this, Action<T> initAction) {
if (initAction != null)
initAction(@this);
return @this;
}
Which could be used similar to Set() as described:
Bucket b = new Bucket() {
Paint = new Paint() {
Red = 0.4;
Blue = 0.2;
Green = 0.1;
}.Init(p => {
p.Mix().Stir();
})
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With