.NET Portable TypeCast
3.1.0.4
A, easy-to-use tested, generic, portable, runtime-extensible, arbitrary type converter library
|
**author** | |
---|---|
**website** | https://github.com/lsauer/dotnet-portable-cast-convert-transform |
**license** | MIT license |
**package** | PM> Install-Package Core.Cast-Transform-Convert |
**description** | An easy to use, portable library for changing between unrestricted, arbitrary data types |
**documentation** | complete reference v3.1.0.2 |
**supported** |
|
Full Version | NuGet | NuGet Install |
---|---|---|
Core.TypeCast | PM> Install-Package Core.Cast-Transform-Convert |
In order to use this library, your application needs .NET Framework 4.5 or higher. Install via two ways:
Install-Package Core.Cast-Transform-Convert
git clone https://github.com/lsauer/dotnet-portable-cast-convert-transform.git
Please visit here for a complete reference, which is also included in the NuGet package.
PM> Install-Package Core.Cast-Transform-Convert
.using Core.TypeCast;
.class MyConverter { ... int MyConverter(string argument) ... }
[ConverterAttribute]
: _public class MyConverter { ... }
_. Subsequently, attribute the converting methods using [ConverterMethodAttribute]
: _public int MyConverter(string attribute){ ... }
__Now, invoke conversions in your code anywhere as follows:
Use library functions which suit the change of type descriptively:
`var complimentary = "GAGTGCGCCCTCCCCGCACATGCGCCCTGACAGCCCAACAATGGCGGCGCCCGCGGAGTC".Transform<Complementary>();`
Provided below is an abbreviated example of what code may look like in your project:
System.ComponentModel
is disliked among developers and overly complicated as is outlined in a code-excerpt from MSDN for implementing a System.String
to System.Drawing.Point
converter:System.ComponentModel.TypeConverter
is absent in Portable Class Libraries
(PCLs), with no immediate portable alternative class filling in for the missing functionality.String
to Point
Interconverter, may be reduced to a few lines of code:The example above, may be written even more concise by adding an Interconverter pair at once:
Indeed, writing this example comes with full IDE code-completion through strong type inference of the Integrated Developer Environment of choice, guiding the developer along the way. If required, culture and context information may be passed as a second function parameter, i.e. the model-argument, otherwise it can be safely omitted.
By the way, using the C# Apply
library (See links), the example becomes even shorter, yet remains comprehensible at a single glance:
Concluding, this library attempts to bridge the gap, with a portable, well developed, documented and tested converter base.
_Note: Assuming that for a high performance loop scenario, carefully in-lined code exceeds the benefit of any additional function invocations, this library is widely and generally applicable.__
The library has three main functions called Cast
, Convert
and Transform
, which are all similar but not the same. Be advised to choose the library's function, which best describes the situation of desired type change between the source type and resulting target data type. Take a look at the definitions. You may also take a glance at a set of suggestions provided under best-practices.
Take note that the library gives plenty of leeway toward individual code style preferences and does not enforce any particular style, other than do not repeat yourself, and keep it short and simple KISS under the moniker of adhering to a pragmatic *single responsibility principle* approach.
Converting methods must have a maximum of one return type and a maximum of two arguments, by design. Create complex, comprehensive converters out of smaller ones, declared using a graph-building library. Incidentally this also serves a purpose of potential parallelization.
By knowing the terminology and applying each library function appropriately to a given context, your code is more readable, entails better IDE code introspection and ultimately is more maintainable.
For instance, if a peer narrowed down a bug to a major underlying type change, then a subsequent investigation can yield the corresponding code units faster.
Following is a table listing the gravity of the type-change, the types involved and the corresponding library functions. The library extends object
with static extension methods. There are many method overloads making the invocations fit a given context and individual coding style.
Type Change | Function Types | Function | _"Try"_-Function | Overloads |
---|---|---|---|---|
[ ~ ] | (TIn, [TIn2]) => TOut | Transform | TryTransform | Weak, Optional model and _"Add"_ function |
[ ~ ] | (TIn, [TIn2]) => TOut | Transform<,> | TryTransform<,> | Strict, Optional model and _"Add"_ function |
[ +~ ] | (TIn, [TOut]) => TOut | CastTo | TryCast | Weak, Optional default-value |
[ +~ ] | (TIn, [TOut]) => TOut | CastTo<,> | TryCast<,> | Strict, Optional default-value |
[ ++ ] | (TIn, [TArg]) => TOut | ConvertTo | TryConvert | Weak, Optional model-value |
[ ++ ] | (TIn, [TArg]) => TOut | ConvertTo<,> | TryConvert<,> | Strict, Optional model-value |
The Try...
functions adhere to the _"Try"_ convention of .NET, returning a boolean value of false
or true
upon success or failure, whilst passing the result value as a reference via out
.
Take a look at the documentation or a glance at the section best practices.
Portable-Singleton
library.TryCast
, following the Try convention of .NETTo
, As
and Cast
, used already "occupied" by LINQ and several Fluid InterfacesProjects, big or small, require casting or conversion of types in one way or another. Aside the option of declaring specific conversion methods in a class as in ToSomeType()
, C# offers implicit and explicit operators, and static extension methods for a given Type, which as a compiler trick is incredibly efficient. This project attempts to provide a comprehensive, easy to use, well tested interface that follows best-practices of conversion through a centralized interface.
The problem with specific class declarations for type conversion is short and simple code maintainability. Whilst custom conventions can be enforced through the use of interfaces, the level of enforcement is insufficient with the lack of declaring non-static methods such as operators whilst offering nothing to prevent unnecessary code repetition, which implies additional code that needs to be maintained and tested.
Say you are implementing ToProtein()
in one class, an implicit operator in another and whilst a peer declared an explicit conversion operator in a third class. You may soon be left with the sobering conclusion that in another derived class that concept does not scale anymore as you continuously fix bugs or get compiler errors, necessitating you to adapt the code. Whilst this may be quickly facilitated at first, it is rarely done so in the required attentive fashion is desired to prevent new bugs.
The benefit of adopting this library into your project is the ability of
Moreover a common conversion interface allows easier testing through integration, unit and mocking tests, with many pre-baked examples that one can copy and paste with few custom adaptations.
Ultimately several threads may concurrently interact with the converter collection instance at runtime. To facilitate correct threading behavior a BlockingCollection
is used.
All library method: Cast
, Convert
and Transform
feature a sibling _"Try"_-method that follows the "Try" convention of .NET . It accepts an object _"value"_ and an out parameter of type T, _"result"_. An attempt is made to cast the value into the result as type T. If the process succeeds, true
is returned, else the converter sets result = default(T)
and returns false
.
Find below an example to provide a glimpse of what the code will look like in a practical example of converting a string to an image:
Which would then be used in productive code as follows:
Another example, excerpted from Example 11:
In the example above, the argument-less method needs to wrapped in order to take an instance of the class as first argument and invoked the method through an instance. This is archived explicitly by declaring the argument passInstance = true
. Moreover the method is aliased to a delegate DNA.ConsensusSequencce
to be invoked through Transform as follows
Or alternatively invoking Transform weakly-typed:
Outlined in the following sections are examples of how to use the various library extension methods. Subsequently you may peek ahead at the best practice paragraph.
In addition, any Integrated Development Environment (IDE) aided with code Introspection such as Sharpdevelop, Xamarin Studio or KDevelop, will let you discover other available method overloads in-tune with your individual code style.
Once the library has been added to your packages.list, you will have a lazy-instancing Singleton class of Type ConverterCollection
. The ConverterCollection
is based on the library dependency Portable-Singleton
, and support full access to IQueryable
and IEnumerable
, in addition to being disposable - a key aspect for supporting server Environments.
Following is an inheritance-graph of the aforementioned relations:
The converter collection instance is accessed through ConverterCollection.CurrentInstance
. The ConverterCollection
is a static on-demand instanced Singleton class containing all converters. The instance is:
Quick Pointers:
ConverterCollection.CurrentInstance
. In case your instance is safe from disposal you may create a local reference.Count
. The count does not include any non-instanced converters attributed with LoadOndemand = true
.Subscribe to PropertyChange
Events such as Count
via the static Singleton event invocator property Singleton<ConverterCollection>.PropertyChanged
Initialize(...)
to scan and load any attributed converters from a class or assembly. Valid argument Types of the method overloads are: Assembly
, Assembly[]
, a string
of the application's NameSpace, a class Type
or any IEnumerable<Type>
.Add(...)
and Add<,>(..)
to add new converters at runtime. Most strongly typed generic functions in the library have weakly typed counterparts.Get(...)
and Get<,>(..)
to lookup a converter.IConverterCollection
as a constructor argument to set any class up for converter dependency injection. Consult the examples for usage cases.For instance, all static functions in ObjectExtensions
delegate a converter-lookup using the following succinct query:
Invoke anywhere via ConverterCollection.CurrentInstance.Get(...)
. As is generally the case in this library, there are strong and weak overloads available for Get
.
Note: Get
can facilitate the lookup and instantiation of Converters grouped into NameSpaces which are attributed as LoadOnDemand. The lookup logic resides in the file ConverterCollectionLookup.cs. To prevent instantiation Get
's parameter loadOnDemand
can be set to false, which is set to false
by default.
Converters can be accessed directly through indexes with arguments of Type
or any integer within the collection range, such that the following statements are all valid:
The wrapper function CanConvertTo
allows intuitively checking whether an converter exists for the given input and output types. The function returns true upon success, else false, and is parametrically identical to the usage of CastTo
:
Additionally ConverterCollection
contains the methods CanConvertFrom
and CanConvertFrom
which take a single type argument, to look up and return true
if any converter was found, else false
as demonstrated bewlow:
Other than a minor performance hit owing to re-instancing, the ConverterCollection
can be destroyed at any time, in accordance with occasional need of multi-threaded server environments e.g. in stateless web-scripts.
In Addition to the LINQ functions provided by the framework or additional packages, the library provides the following filters
WithFrom(TypeInfo typeFrom)
WithTo(TypeInfo typeTo)
WithArgument(TypeInfo typeArgument)
WithBaseType(TypeInfo typeBase)
WithStandard(bool? isStandard)
WithDefaultFunction(bool? hasDefaultFunction)
WithFromIsGenericType(bool? typeFromIsGenericType)
WithToIsGenericType(bool? typeToIsGenericType)
WithFunctionName(string functionName)
WithConverterAttributeName(string attributeName)
Be advised that all Get
functions are a Facade, which at its core wrap these aforementioned Query filters. These filter functions reside in the static class ConverterCollectionFilters
. The Query function ApplyAllFilters(typeTo:..., typeArgument:..., )
in turn, wraps all LINQ Query filter functions serving as a a central hub, with the arguments
and Types provided in the list above.
The following example will list all loaded and instanced converters currently residing within the collection, however none which are back-listed by means of a ConverterAttribute
parameter set to lazy or on-demand Loading. Such converters are added to the collection only upon a request to their particular namespace collection set in the attribute.
This loop should yield a result that is similar to the abbreviated listing below, wherein Converter
2is a converter instance not specifying an Argument-Type (i.e. set to
object`):
LINQ is fully applicable to the collection. For instance, you may loop over all results at any time such as: foreach(var item in cc.WithFrom(typeof(Point))).WithToIsGenericType()
This will lookup and single out all converters with a source type of Point
and any target type that is comprised out of generic parameters.
_Note: Many queries are deferred and are not applied and executed until an Enumerator, Count, ToArray, etc is requested.__
Use Cast whenever an unrestricted Type A is to be changed in an unrestricted Type B, without an inferred particular relationship or complexity. Use TryCast to prevent raising exceptions during the invocation of the custom conversion-logic.
Following are further examples, demonstrating that the ConverterCollection
can be disposed at any time, as long as the converters are discoverable through attributes in the included assemblies.
The libraries numerous method overloads allow flexibility towards personal code styles whilst remaining strongly descriptive.
Treatment of Nullables<>
is complicated as boxing a non-null nullable value type boxes the value type itself (See References at the end). CastTo
includes a specific generic overload just for non-null Nullable value-types, which is invoked in each of the following methods:
You can declare a custom NumberFormatter during adding, load serialized functions, and precede casting by a value-boxing step.
In analogy to the Try convention of the .NET framework, TryCast may be more appropriate for casting, which suppresses any exceptions during the casting process and returns a boolean success value instead, whilst passing the casting result by reference.
Following are two examples:
The convert methods ConvertTo
and TryConvert
, can involve up to three different types, to convert an arbitrary input type to another arbitrary output type, using an optional second model argument, which encapsulates all data required for the custom conversion function.
Following is an example absent of accompanying notes:
In analogy to the Try convention of the .NET framework, TryConvert may be more appropriate for converting, which suppresses any exceptions during the converting process and returns a boolean success value instead, whilst passing the conversion result by reference.
Following is an example, using the converter function from the example provided in the preceding section:
Provided below is a code example for a more involved and comprehensive converter example which take a String
and converts it into a MemoryStream
of an Image. The code is provided in full in the examples folder of the library. For brevity's sake the EventModelBase
is excluded from being listed herein.
Following, the _"model"_ class is declared, with a simplified inheritance model, along with events required.
Next, the actual converter logic is implemented and added. Please note that the example is written such as to purposefully demonstrate features of the library.
In an actual project, be advised to put a converter of this complexity in a separate class and split it up into several smaller converter-units, pragmatically adhering to the Single Responsibility Principle.
On rare occasions, a contextual data structure may be required which provides meta-data about the converting-process. Such data is provided by the ConvertContext
, and passed to the Convert function in question, when the parameter withContext
of ConvertTo
or TryConvert
is set to true
:
This will pass a ConvertContext
instance to the converter similar to the following:
Note: Argument
is set to null
, which is the default
if the type is object
(Boxed)
In that case the model-value that is passed to the converter function upon invocation will be wrapped in a ConvertContext
instance, which implements IConvertContext
. The field-names follow the names of the arguments of the extension methods in the class ObjectExtension
.
Important: The requirement for the converter function that can be invoked through ConvertTo
is that the second argument must be of type object
, as follows:
Note: To simply get the Converter instance within the converting function, prefer using ConverterCollection.CurrentInstance.Get(...)
over obtaining an IConvertContext
instance.
In case the converter function should always be invoked by ConvertTo(...withContext: true )
or TryConvert(...withContext: true )
you are advised to set the Type of the second function argument as IConvertContext
. Thus ensuring that an exception is thrown if the converter is not called with a context and avoiding type boxing / casts.
Otherwise a System.InvalidCastException
exception will be thrown.
_Note: The decision for implementing a converting-context as an opt-in rather, than inherently into the architecture of the Converter
base-classes, is separation from invocation time arguments for thread-safety, along with avoidance of additional overhead for a feature rarely required.__
Transform is useful in situations wherein the input and output type are similar or the same. Aside from linguistics the implementation is similar. All Types involved in the conversion must be from the same namespace.
If the optional parameter strictTypeCheck
is set to true
, an exception will be thrown if the input and output types do not match. This does not hold true for the Try version, which has no optional strictTypeCheck
argument.
In the following example, a converter transposing a 2x2 Square matrix is implemented. Aptly, Transform
is invoked as the output and input types match.
Transformations are different from Convert
and Cast
operations in that there can be disambiguate functions, all of which operate on the same input-, output- and model parameter types, yet may yield completely different values.
As such, converters should be assigned by named aliases or delegates. Follow best-practices by using a transform enumerable. However a string argument may be passed as alias as well.
Additionally, best-practices recommend that an assertively named delegate is declared, as explicatively shown in the following code sample:
Example for simple matrix operations
After adding the custom transformer-algorithm, the given matrix may be transformed by passing the delegate
type of the transformer function, and declaring the output Type as the second generic parameter, or alternatively following up with a subsequent cast operation as shown below:
Alternatively, the delegate may be passed as a type argument, in case the output and input types do match:
The example using Transpose2x2
will yield the transposed of input
matrix2x2` as follows:
When the input and output types do not match, a subsequent implicit or explicit cast operation is required as follows:
Additionally, particularly with interactive-shells in mind, Transform
allows adding an aliased function and computing in one invocation, with the full benefit of strong typed IDE introspection, as follows:
A real-case scenario of using Transform is provided in Example11.
In analogy to the _"Try"_ convention of the .NET framework, TryTransform
may be more appropriate for transformations, which suppresses any exceptions during the transforming process and returns a boolean success value instead, whilst passing the transformation result by reference.
Following is an example, using the transformation function from the example provided in the preceding section:
Any custom written converter logic is brought into the context of a strong-type generic converter container instance, with the abstract base-class Converter
. In general converters are instanced internally by a Factory during the process of adding custom-converters to the static collection ConverterCollection
by various means. The easiest way of adding converters is by attributing methods throughout the assembly, through the attributes ConverterAttribute
and ConverterMethodAttribute
, in just this nesting order.
Many intuitive and concise ways of adding converters to the ConverterCollection
exists and will be provided in brief in the subsequent listings. Be advised that by design Converters cannot be removed, other than through the methods provided by the BlockingCollection
using ConsumingEnumerator
.
Following are the most common ways of adding converters at runtime:
ConverterCollection.CurrentInstance.Initialize(Assembly assembly); ConverterCollection.CurrentInstance.Initialize(Type className); ...
ConverterCollection.CurrentInstance.Add( (string s) => new Point(int.Parse(s?.Split(',').First()), int.Parse(s?.Split(',').Last())), p => $"{p.X},{p.Y}" );
delegate double Entropy(byte[] data, EntropySettings model); ConverterCollection.CurrentInstance.Add<byte[], EntropySettings, double, Entropy>((da, se) => { ... });
object
) var fileStream = new FileStream(@"converters.dat", FileMode.Open); var binaryFormatter = new BinaryFormatter(); var func = binaryFormatter.Deserialize(fileStream ); ConverterCollection.CurrentInstance.Add(func);
ConverterCollection.CurrentInstance.Add((Point[] pts) => new Perimeter(pts)); //with generics ConverterCollection.CurrentInstance.Add((Point a, someGenericClass<Point, ConverterAttribute> b) => { return new someGenericClass<Point, ConverterAttribute>(); });
public class ShapeMath { .... } ConverterCollection.CurrentInstance.Add<Point[], Perimeter, ShapeMath>((Point[] pts) => new Perimeter(pts));
[Converter (dependencyInjection: true)] public class ConverterDefaults { public ConverterDefaults(IConverterCollection collection) : base(collection) { this.NumberFormat = ConverterCollectionSettings.DefaultNumberFormat.Clone() as NumberFormatInfo; collection.Add<object, int>(o => int.Parse(o?.ToString() ?? string.Empty, this.NumberFormat), this.GetType()) } }
By invoking AddStart(...)
on the ConverterCollection
instance, a set of related converters can be added in a deferred manner, such that a set of converters share specific ConverterColllectionSettings
, and a mutual reference to a CancellationToken
for multi-threaded situations, as well as other parameters.
As the method is deferred, the operation does not complete until the method End()
is invoked, as shown in the following example:
Reading the sections thus far you are already familiarized with the syntax and possibilities of this library. Generally speaking best practices for using this library and conversion logic at large, breaks down to:
ConvertContext
Following in brief are graphs and short descriptions of important classes of the project. Detailed descriptions are provided in full in the documentation.
The library uses two custom attributes. ConverterAttribute
ascribable to a class
and ConverterMethodAttribute
ascribable to a delegate
. The attributes must be nested in just this order.
Underlying the converters are a strongly typed container, declaring three types. TIn
, TOut
, TArg
, with TArg
being optionally used and set to object
as a default value.
For higher performance, one can first directly fetch a converter through the common LINQ query interface and subsequently reference the converter function for direct invocation within a loop or other code parts which require attention towards performance. Additionally, for performance-critical code part you may want to explicitly invoke the converter, bypassing the library altogether and enable compiler-inlining via [MethodImpl(MethodImplOptions.AggressiveInlining)]
.
If you need assistance with your project codebase or custom implementations in regards to synchronization of the Converters across memory-contexts or other barriers, eventing and notifications, parallel processing, profiling and performance reports or anything else tangential to this library, please do not hesitate further and get in touch.
This library is being continously tested and improved using the NUnit Test Framework. If you run into a bug, please push a new issue here.
Copyright 2013-2016 by Lorenz Lo Sauer - MIT License
Please let me know about your project if I missed to list it here, or push an edit yourself.