EasyWorks.DataCenter 1.5.0

dotnet add package EasyWorks.DataCenter --version 1.5.0
NuGet\Install-Package EasyWorks.DataCenter -Version 1.5.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EasyWorks.DataCenter" Version="1.5.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EasyWorks.DataCenter --version 1.5.0
#r "nuget: EasyWorks.DataCenter, 1.5.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install EasyWorks.DataCenter as a Cake Addin
#addin nuget:?package=EasyWorks.DataCenter&version=1.5.0

// Install EasyWorks.DataCenter as a Cake Tool
#tool nuget:?package=EasyWorks.DataCenter&version=1.5.0

EasyWorks.DataCenter

Introduction

To put it simple, the Repository in this library is a advanced version of Lazy<T> backed with Dictionary<TKey,TValue> and ValueTuple with Function Programming paradigm to:

  • Avoid writing boiler plate codes.
  • Enable efficient caching of ANY types of values associated with ANY types of keys.
  • Mandatory Factory method to create values for keys not presented in the Repository.
  • Extended versions like Repository<TKey, TValue1, TValue2, TValue3> can keep multiple values associated with the same key.
  • Multi-keys version like MRepository<TKey1, TKey2, TKey3, TValue> can flat combinations of multiple input parameters to simplify complex switchings.

Getting Started with Examples

Simplest Lazy Loader

For expensive operations like reflection of types to get their PropertyInfo, it is preferred to operate once and keep the result in a dictoary for later use as codes below:

private static IDictionary<Type, PropertyInfo[]> cache = new Dictionary<Type, PropertyInfo[]>;

public static PropertyInfo[] GetProperties(Type type)
{
    if (!cache.ContainsKey(type))
    {
        cache.Add(type, type.GetProperties());
    }
    return cache[type];
}

var propertyInfos = GetProperties(typeof(MyClass));

It can be replaced with:

public static readonly Repository<System.Type, PropertyInfo[]> TypeProperties =
            new Repository<System.Type, PropertyInfo[]>(t => t.GetProperties());

var propertyInfos = TypeProperties.Get(typeof(MyClass));

Cache of multiple values

With ValueTuples, both Keys and Values of the Repository can be of multiple values that can make Lazy loading more efficient.

Stick with the use of PropertyInfo by assuming it would be ued to extract value of the class instances, following code snippet shows how to cache multiple values and turn the Repository into a higher-order-functions.

public static readonly Repository<System.Type, (PropertyInfo[], Dictionary<string, Func<object, object>>)> TypeProperties =
    new Repository<System.Type, (PropertyInfo[], Dictionary<string, Func<object, object>>)>(ParseType);

internal static (PropertyInfo[], Dictionary<string, Func<object, object>>) ParseType(System.Type type)
{
    PropertyInfo[] propertyInfos = type.GetProperties();
    Dictionary<string, Func<object, object>> extractors = propertyInfos.ToDictionary(
        pi => pi.Name,
        pi => (Func<object, object>)(obj => pi.GetValue(obj))
    );
    return (propertyInfos, extractors);
}

public static bool TryGetPropertyValue(object source, string propertyName, out object value)
{
    if (source is null || propertyName is null)
    {
        value = source;
        return false;
    }

    (PropertyInfo[] _, Dictionary<string, Func<object, object>> extractors) = TypeProperties.Get(source.GetType());

    if (extractors.ContainsKey(propertyName))
    {
        value = extractors[propertyName](source);
        return true;
    }

    string caseIgnoredName =
        extractors.Keys.SingleOrDefault(k => k.Equals(propertyName, StringComparison.CurrentCultureIgnoreCase));

    if (caseIgnoredName is null)
    {
        value = source;
        Console.WriteLine($"Failed to locate property of {propertyName} from object {source}");
        return false;
    }

    value = extractors[caseIgnoredName](source);
    return true;
}

Given you want to access the property of a class instance by its name, following codes would be enough:

bool result = TryGetPropertyValue(myClassInstance, "propertyName", out object value);

result = TryGetPropertyValue(oneDay, "Year", out object year)

Here is the benefits:

  • The myClassInstance can be of any type, the Repository would create an entry against its type once only.
  • Each PropertyInfo would be converted to an anonymous function that shall be faster than calling PropertyInfo.GetValue() directly.
  • The higher order function ParseType(System.Type type) can be tested easily.

Flattened Dictionary for complex choices

See MRepository example.

There are two places to inject your business logic:

  • Insert the logic to compare concerned logic into the PredefinedComparers
  • Create comparers at run-time in the factory method ComparerFactory(Type typeA, Type typeB)

With the flattened structure to organise the business logic with the Repository, there are some benefits:

  • Easy to extend functionalities.
  • New business logic could be created with the factory at run time, that is ideal to handle similar but different types like different Enums.
  • Least codes can be tested easily.

More thoughts

The idea of this project came when I tried to apply Functional Programming paradigm into my coding practice: if you treat functions as values of a dictionary referred by corresponding keys, a lot of technical challenges could be handled gracefully.

This idea has been applied successfully with other languages (JAVA, Python and TypeScript) to make my life much easier. Before exploring some other topics with .NET core, refactoring this project might save me some time in long run and hope this document can also help others to save unnecessary codes.

Enjoy coding.

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 is compatible. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 is compatible.  net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETCoreApp 3.1

    • No dependencies.
  • .NETFramework 4.7.2

    • No dependencies.
  • .NETFramework 4.8

    • No dependencies.
  • .NETStandard 2.0

    • No dependencies.
  • .NETStandard 2.1

    • No dependencies.
  • net5.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.5.0 451 8/11/2021
1.2.0 287 8/11/2021
1.1.0 354 5/24/2021
1.0.0 1,148 1/29/2018

Generic caching with factory method, and optional initial values.