Shadow Quack enables immutable interface contracts to be used, by using a dynamic implementation. Shadow Quack allows for powerful duck typing, interface facade merging and functional immutable update/merge-operations. Providing similar functionality to C# 9/10 record, with the advantage of also being able to use inheritance.
$ dotnet add package Shadow.QuackThis package allows the easy population of values directly from an existing object or dynamic to a dynamically implemented immutable interface it also allow the merge of several objects or dynamic retuning a new object.
Though it is possible to use interfaces with both getters and setters the intent is for use with interfaces with get-only properties making the contract immutable.
The combination of these two functions makes an interface the only required data contract. The merge functionality allows for functional operations to return an new object with the values from one or more source. The use of a dynamic in this context allows for the simple overriding of one or more properties. The resulting new object has the new property values set with the remaining properties carrying the values through from the source.
There are also many abstraction applications where being able to Duck Type data to an interface from an object outside of your control turns out to be very useful.
public interface IData
{
int Property1 { get; }
string Property2 { get; }
}
var data = Duck.Implement<IData>(new
{
Property1 = 100,
Property2 = "This"
});
var result = Duck.Implement<List<string>>(new[] { 1, 2, 3, 4 });
var result = Duck.Implement<IEnumerable<string>>(new[] { 1, 2, 3, 4 });
Interfaces can be used value definitions, as required, using the same priciples as any other Duck.Implement<T>() Call.
var result = Duck.Implement<IDictionary<int, string>>(new[] { new { Key = 1, Value = "value" } });
var result = Duck.Implement<Dictionary<int, string>>(new[] { new { Key = 1, Value = "value" } });
Interfaces can be used for both key and value definitions, as required, using the same priciples as any other Duck.Implement<T>() Call.
var data = Duck.Implement<IData>(new
{
Property1 = 100,
Property2 = "This"
});
var merged = Duck.Merge<IData>(data, new {Property2 = "That"});
This results in Property1 = 100, and Property2 = "That", this is useful if you want to take an existing object and just update one property, this creates a new object keeping the immutability of the original source. You can merge several objects on a last one wins basis.
var data = Duck.Implement<IData>(new
{
Property1 = 100,
Property2 = "This"
});
var merged = Duck.Merge<IData>(data,
new { Property2 = "That" },
new { Property1 = 35 },
new { Property2 = "Other" });
This results in Property1 = 35, and Property2 = "Other"
The following extensions are exposed for general use.
Allows casting of a string value to the below set of native types, with an optional default value
var stringValue = "123";
var intValue = stringValue.CastStringTo<int>, -1);
intValue will be 123, passing an empty or null string would return -1.
The cast is case insensitive and will interpret a space " " as an underscore "_" e.g. My Enum Value = My_Enum_Value
public enum MyEnum
{
TypeOne = 1,
TypeThree = 3,
Type_Four = 4
}
...
var stringValue = "type four";
var intValue = stringValue.CastStringToEnum<MyEnum>();
This will cast it to MyEnum.Type_Four.
The following extensions give a cleaner usage of the Merge<T>() function
var config = Duck.Implement<IConfig>(new
{
Int64Value = "12345",
NumericValue = "123"
});
var result = config.MergeWithTo<IMergedConfig>(new { NumericValue = "456" });
var config = Duck.Implement<IConfig>(new
{
Int64Value = "12345",
NumericValue = "123"
});
config = config.MergeWith(new { NumericValue = "456" });
If <T> is not an interface, with the exceprion of the cast extensions, an InterfaceExpectedException will be thrown.
Shadow Quack also allows extension via Proxy types, this is done via an IProxy interface and a custom Type and Property classes.
The extensions written already, provided in separate packages, include;
There is also a further extension of this ecosystem in Quack API which includes custom implementation of JsonConverter and JsonConvertorFactory, that wrap the JSON proxy, to allow the use of Interfaces as return and parameter types in ASP .Net API calls.