Pattern Binder – BinderDirector – C#

Very usefull in ressource aggregation, here you will find a code snippet that can be integrated in Inversion Of Control pattern using Interface Segregation Principle of SOLID designing principles.

public interface IBinder
    {

    }

    public interface IBinder<in T> : IBinder
    {
        void Bind(IEnumerable<T> bounds);
    }

    public interface IBinderDirector<T> 
    {
        IBinder<T>[] DataBinders { get; }

        void Bind(IEnumerable<T> bounds);
        void BindParallel(IEnumerable<T> bounds);
        void BindOnlySelected(IEnumerable<T> bounds, IEnumerable<Type> selecteds);
        void BindAllExcept(IEnumerable<T> bounds,IEnumerable<Type> excepts);
        void BindOnlySelectedParallel(IEnumerable<T> bounds,IEnumerable<Type> selecteds);
        void BindAllExceptParallel(IEnumerable<T> bounds,IEnumerable<Type> excepts);
    }


    public class BinderDirector<T> : IBinderDirector<T>
    {
        private readonly IBinder<T>[] _dataBinders;
        public IBinder<T>[] DataBinders => throw new NotImplementedException();

        public BinderDirector(IEnumerable<IBinder<T>> dataBinders)
        {
            _dataBinders = (dataBinders ?? Enumerable.Empty<IBinder<T>>()).ToArray();
        }

        public void Bind(IEnumerable<T> bounds)
        {
            var boundAsList = bounds.ToArray();
            foreach(var dataBinder in _dataBinders) dataBinder.Bind(boundAsList);
        }

        public void BindAllExcept(IEnumerable<T> bounds, IEnumerable<Type> excepts)
        {
            var boundArray = bounds.ToArray();
            var runnableDataBinders = _dataBinders.Where(m => excepts.Any(s => !s.IsAssignableFrom(m.GetType())));
            foreach (var dataBinder in runnableDataBinders) dataBinder.Bind(bounds);
        }

        public void BindAllExceptParallel(IEnumerable<T> bounds, IEnumerable<Type> excepts)
        {
            var boundArray = bounds.ToArray();
            var runnableDataBinders = _dataBinders.Where(m => excepts.Any(s => !s.IsAssignableFrom(m.GetType())));
            Parallel.ForEach(runnableDataBinders, (runnableDataBinder, index) => runnableDataBinder.Bind(bounds));
        }

        public void BindOnlySelected(IEnumerable<T> bounds, IEnumerable<Type> selecteds)
        {
            var boundArray = bounds.ToArray();
            var runnableDataBinders = _dataBinders.Where(m => selecteds.Any(s => s.IsAssignableFrom(m.GetType())));
            foreach (var dataBinder in runnableDataBinders) dataBinder.Bind(bounds);
        }

        public void BindOnlySelectedParallel(IEnumerable<T> bounds, IEnumerable<Type> selecteds)
        {
            var boundArray = bounds.ToArray();
            var runnableDataBinders = _dataBinders.Where(m => selecteds.Any(s => s.IsAssignableFrom(m.GetType())));
            Parallel.ForEach(runnableDataBinders, (runnableDataBinder, index) => runnableDataBinder.Bind(bounds));
        }

        public void BindParallel(IEnumerable<T> bounds)
        {
            var boundArray = bounds.ToArray();
            Parallel.ForEach(_dataBinders, dataBinder => dataBinder.Bind(boundArray));
        }
    }

Underneath you will find an example of concrete implementation. The thing which is interesant, is that the BinderDirector is a pure interpretation of the Interface Segregation Principle :

public interface IWithBindPropertiesA
    {
        string IdentifierA { get; }
        string ReturnedPropertyValueA { set; }
    }

    public interface IWithBindPropertiesB
    {
        string IdentifierB { get; }
        string ReturnedPropertyValueB { set; }
    }

    public class ConcreteImplementation : IWithBindPropertiesA, IWithBindPropertiesB
    {
        public new string IdentifierA { get; set; }

        public new string ReturnedPropertyValueA { get; set; }

        public new string IdentifierB { get; set; }

        public new string ReturnedPropertyValueB { get; set; }
    }


    public class BindPropertiesABinder : IBinder<IWithBindPropertiesA>
    {
        public void Bind(IEnumerable<IWithBindPropertiesA> bounds)
        {
            foreach(var bound in bounds)
            {
                bound.ReturnedPropertyValueA = bound.IdentifierA == "A"?"A":"unknown"; //In fact you can do what you want
            }
        }
    }
    public class BindPropertiesBBinder : IBinder<IWithBindPropertiesB>
    {
        public void Bind(IEnumerable<IWithBindPropertiesB> bounds)
        {
            foreach (var bound in bounds)
            {
                bound.ReturnedPropertyValueB = bound.IdentifierB == "B" ? "B" : "unknown"; //In fact you can do what you want
            }
        }
    }

    public static class Program
    {
        public static void Main(string[] argc)
        {
            var binderDirector = new BinderDirector<ConcreteImplementation>(new IBinder<ConcreteImplementation>[] {
                new BindPropertiesABinder(),
                new BindPropertiesBBinder()
            });
        }
    }

LMAX Disruptor Pattern

Disruptor pattern stem it’s performance due to the current architecture of computers.

The architecture relavance remain in :

  • Zero memory allocation after initial setup
  • Push based consumers
  • The feeder is attached to only one thread and have minimal workload (it dispatch the data – rolling cache)

Everything mentioned upper make the LMAX Disruptor pattern a low latency pattern.

.Net implementation is available here :

https://github.com/disruptor-net/Disruptor-net

Fast exponentiation – C# implementation

When you want to power very fast a number in order to meet some performance constraints the standard math power algorithm implementation can be overwhelmed.

Fast exponentiation algorithm take advantage of the computer binary memory structure in order to avoid complexe arithmetic computation.

This algorithm allows you to raise any number to a natural power for a reduced number of multiplications.

For any number and even degree , the identity holds:

= (x n / 2 = x n / 2 ⋅ x n / 2

For the case of odd degree, it is enough to lower it by one:

= x n – 1 ⋅ x , while (n – 1) is an even number.

Fast exponentiation – recursive implementation (beware of stack overflow)

static long RecursivePower(long x, int n)
{
    if (n == 0) return 1; //in any case Power(x,0) give 1
    if ((n & 1) == 0) //Bit comparison to check if is an even number
    {
        var p = RecursivePower(x, n >> 1); //Equivalent to Power(x, n/2)
        return p * p;
    }
    else
    {
        return x * RecursivePower(x, n - 1);
    }
}

Fast exponentiation – Iterative implementation

static long IterativePower(long x, int n)
{
    var result = 1L;
    while (n> 0)
    {
        if ((n & 1) == 0) //Bit comparison to check if is an even number
        {
            x *= x;
            n >>= 1;
        }
        else
        {
            result *= x;
            --n;
        }
    }
    return result;
}

C++ equivalent implementation of C# interface

Sometimes when learning from one language to another one, you may have to adapt your habit to the paradigm of language and it’s evolution degree.

Interface for C# developer is one of the pillar of SOLID Implementation Principles. In the C++ development the equivalence is not trivial enough in my opinion.

Response : virtual C++ classes

Interface in C#

//ICompte.cs file
public interface ICompte
{
    double retirerArgent(double montant);
    double ajouterArgent(double montant);
    double Solde {get;}
}

C++ equivalent implementation

//ICompte.h file
class ICompte
{
public:
	virtual double retirerArgent(double montant) = 0;
	virtual double ajouterArgent(double montant) = 0;
	virtual double getSolde() = 0;
};

TestValueFactory C# & Mapper Tests

When you’re writing tests, especially mapper tests, knowing the value which is mapped is not of great use. What you’re testing is that the value is correctly mapped, or bound to the accurate location in destination.

Mapping is very often not considered by young developer (and sometimes senior developer) as a source of error. In my opinion, actually it is. Thus, that’s why I am sharing those code snippets to help people writing tests on mappers.

Keep this sentence in mind, when writing tests in group code project : “Backup your a** !”. Everything can be a source of error.

The TestValueHelper implementation provide issue resolution to handle random type value generation. This class is certainly to be developped.

using System;
using System.Collections.Generic;

namespace maximilienzakowski.com.TestToolKit
{
    public static class TestValueFactory
    {
        private static readonly Random _random = new Random();

        public static int GetRandomIntValue()
        {
            return _random.Next();
        }

        public static int GetRandomIntValue(int minValue, int maxValue)
        {
            return _random.Next(minValue, maxValue);
        }

        public static int GetRandomIntValue(int[] except)
        {
            var exceptSet = new HashSet<int>(except??new int[] { });
            int result;
            do
            {
                result = _random.Next();
            }
            while (exceptSet.Contains(result));
            return result;
        }

        public static DateTime GetRandomDateTimeValue()
        {
            DateTime start = new DateTime(1970, 1, 1);
            int range = (DateTime.Today - start).Days;
            return start.AddDays(_random.Next(range))
                .AddHours(_random.Next(0, 24))
                .AddMinutes(_random.Next(0, 60))
                .AddSeconds(_random.Next(0, 60));
        }

        public static double GetRandomDoubleValue()
        {
            return _random.Next() * _random.NextDouble();
        }

        public static decimal GetRandomDecimalValue()
        {
            double RandH, RandL;
            do
            {
                RandH = _random.NextDouble();
                RandL = _random.NextDouble();
            } while ((RandH > 0.99999999999999d) || (RandL > 0.99999999999999d));
            return (decimal)RandH + (decimal)RandL / 1E14m;
        }
        public static decimal GetRandomDecimalValue( decimal minValue, decimal maxValue)
        {
            return GetRandomDecimalValue() * (maxValue - minValue) + minValue;
        }

        public static T GetRandomEnumValue<T>() where T : System.Enum
        {
            var values = Enum.GetValues(typeof(T));
            return (T)values.GetValue(_random.Next(0,values.Length-1));
        }

        public static string GetRandomStringValue()
        {
            return GetRandomStringValue(20);
        }

        public static string GetRandomStringValue(int length)
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            return new string(Enumerable.Repeat(chars, length).Select(s => s[_random.Next(s.Length)]).ToArray());
        }
    }
}

Tested code snippet which is a builder + mapper association :

public class ItemToInvoiceRowConverter2
    {
        public IInvoiceRow Convert(IItem converted)
        {
            if (converted == null) return null;
            var result = new InvoiceRow();
            //result.Number = converted.Id;
            result.IdItem = converted.Id;
            result.Kind = converted.Kind;
            result.Label = converted.Name;
            //result.Vat = converted.;
            result.Unit = converted.Unit;
            result.UnitPrice = converted.Price;
            //result.Discount = converted.Id;
            //result.Quantity = converted.Id;
            return result;
        }
    }

Unit test code :

using myApplication.Converters;
using myApplication.Models;
using maximilienzakowski.com.TestToolKit;
using Moq;
using NUnit.Framework;

namespace myApplication.Tests
{
    [TestFixture]
    public class ItemToInvoiceRowConverterTests
    {
        private ItemToInvoiceRowConverter2 BuildTested()
        {
            return new ItemToInvoiceRowConverter2();
        }

        [Test]
        public void ConvertShouldReturnNullWhenConvertedIsNull()
        {
            var tested = BuildTested();
            var result = tested.Convert(null);
            Assert.IsNull(result);
        }


        [Test]
        public void ConvertShouldConvertAndMapAccurately()
        {
            var converted = new Mock<IItem>();
            var id = TestValueFactory.GetRandomIntValue();
            var kind = TestValueFactory.GetRandomStringValue();
            var name = TestValueFactory.GetRandomStringValue();
            var unit = TestValueFactory.GetRandomStringValue();
            var price = TestValueFactory.GetRandomDecimalValue();

            converted.SetupGet(x => x.Id).Returns(id);
            converted.SetupGet(x => x.Kind).Returns(kind);
            converted.SetupGet(x => x.Name).Returns(name);
            converted.SetupGet(x => x.Unit).Returns(unit);
            converted.SetupGet(x => x.Price).Returns(price);
            var tested = BuildTested();
            var result = tested.Convert(converted.Object);
            Assert.IsNotNull(result);
            Assert.AreEqual(id, result.IdItem);
            Assert.AreEqual(kind, result.Kind);
            Assert.AreEqual(name, result.Label);
            Assert.AreEqual(unit, result.Unit);
            Assert.AreEqual(price, result.UnitPrice);

            converted.VerifyGet(x => x.Id,Times.Once);
            converted.VerifyGet(x => x.Kind, Times.Once);
            converted.VerifyGet(x => x.Name, Times.Once);
            converted.VerifyGet(x => x.Unit, Times.Once);
            converted.VerifyGet(x => x.Price, Times.Once);
            converted.VerifyNoOtherCalls();
        }
    }
}

Hope this will help someone else than me…

Linq.Net To SqlLite Converter implementation attempt

Sometimes when you want to manage your code efficiency you may need to optimize some .Net implementation.

Instead of relying on Entity Framework or other closed binary implementation you may need to implement your specific AdHoc behavior, this is a good example to do so…

Underneath you will find my proposal to implement a Linq to SQLLite conversion with C#. This is a generic code and may be optimized in many way for your implementation needs. It use System.Linq but remediation can be done very easily to not depend on this library.

using System.Collections;
using System.Linq.Expressions;
using System.Reflection;

namespace Com.Maximilienzakowski.Dal.Common.Repository
{
    public interface ICommandTextWhereArgBuilder<T> where T : class
    {
        string Build(Expression<Func<T, bool>> whereExpression);
        string Build(Expression node);
    }

    public class SqliteCommandTextWhereArgBuilder<T> : ICommandTextWhereArgBuilder<T> where T : class
    {
        private readonly IDictionary<ExpressionType,ISqlLiteWhereCommandExpressionBuilder> _whereExpressionsBuilders;
        private readonly IObjectToSqlValueConverter _objectToSqlValueConverter = new ObjectToSqlValueConverter();
        public SqliteCommandTextWhereArgBuilder()
        {
            var sqlLiteWhereCommandExpressionBuilders = new ISqlLiteWhereCommandExpressionBuilder[]
            {
                new SqlLiteLambdaWhereCommandExpressionBuilder<T>(this),
                new SqlLiteAndWhereCommandExpressionBuilder<T>(this),
                new SqlLiteAndAlsoWhereCommandExpressionBuilder<T>(this),
                new SqlLiteEqualWhereCommandExpressionBuilder<T>(this),
                new SqlLiteContainsCallWhereCommandExpressionBuilder<T>(this),
                new SqliteConvertAccessWhereCommandExpressionBuilder<T>(this),
                new SqliteMemberAccessWhereCommandExpressionBuilder<T>(_objectToSqlValueConverter),
                new SqliteConstantWhereCommandExpressionBuilder<T>(_objectToSqlValueConverter),
                
            };
            _whereExpressionsBuilders = sqlLiteWhereCommandExpressionBuilders.ToDictionary(m => m.ExpressionType, m => m);
        }


        public string Build(Expression<Func<T, bool>> whereExpression)
        {
            var expression = whereExpression.Reduce;
            return Build(expression());
        }

        public string Build(Expression node)
        {
            if(!_whereExpressionsBuilders.TryGetValue(node.NodeType, out var whereExpressionBuilder)) return "UNDEFINED EXPRESSION BUILDER";
            return whereExpressionBuilder.Build(node);
        }
    }

    public class SqlLiteContainsCallWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {
        private readonly ICommandTextWhereArgBuilder<T> _commandTextWhereArgBuilder;
        public ExpressionType ExpressionType => ExpressionType.Call;
        public SqlLiteContainsCallWhereCommandExpressionBuilder(ICommandTextWhereArgBuilder<T> commandTextWhereArgBuilder)
        {
            _commandTextWhereArgBuilder = commandTextWhereArgBuilder ?? throw new ArgumentNullException(nameof(commandTextWhereArgBuilder));
        }
        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType.Call) return string.Empty;
            var methodCallNode = (MethodCallExpression)node;
            if (methodCallNode.Method.Name != "Contains") return "UNHANDLED METHOD CALL";
            if(methodCallNode.Arguments.Count == 2)
            {
                return $"({ _commandTextWhereArgBuilder.Build(methodCallNode.Arguments.Last())} IN {_commandTextWhereArgBuilder.Build(methodCallNode.Arguments.First())})";
            }
            else if(methodCallNode.Arguments.Count == 1)
            {
                var left = _commandTextWhereArgBuilder.Build(methodCallNode.Arguments.Single());
                return $"({left} IN {_commandTextWhereArgBuilder.Build(methodCallNode.Object)})";
            }
            return "UNHANDLED CONTAINS METHOD CALL";
        }
    }


    public class SqlLiteLambdaWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {
        private readonly ICommandTextWhereArgBuilder<T> _commandTextWhereArgBuilder;
        public ExpressionType ExpressionType => ExpressionType.Lambda;
        public SqlLiteLambdaWhereCommandExpressionBuilder(ICommandTextWhereArgBuilder<T> commandTextWhereArgBuilder)
        {
            _commandTextWhereArgBuilder = commandTextWhereArgBuilder ?? throw new ArgumentNullException(nameof(commandTextWhereArgBuilder));
        }
        public string Build(Expression? node)
        {
            if(node.NodeType != ExpressionType.Lambda) return string.Empty;
            var lambdaNode = (LambdaExpression)node;
            return _commandTextWhereArgBuilder.Build(lambdaNode.Body);
        }
    }

    public class SqlLiteAndWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {
        private readonly ICommandTextWhereArgBuilder<T> _commandTextWhereArgBuilder;

        public ExpressionType ExpressionType => ExpressionType.And;
        public SqlLiteAndWhereCommandExpressionBuilder(ICommandTextWhereArgBuilder<T> commandTextWhereArgBuilder)
        {
            _commandTextWhereArgBuilder = commandTextWhereArgBuilder ?? throw new ArgumentNullException(nameof(commandTextWhereArgBuilder));
        }

        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType) return string.Empty;
            var binaryExpressionType = (BinaryExpression)node;
            return $"({_commandTextWhereArgBuilder.Build(binaryExpressionType.Left)}) AND ({_commandTextWhereArgBuilder.Build(binaryExpressionType.Right)})";
        }
    }

    public class SqlLiteAndAlsoWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {
        private readonly ICommandTextWhereArgBuilder<T> _commandTextWhereArgBuilder;

        public ExpressionType ExpressionType => ExpressionType.AndAlso;
        public SqlLiteAndAlsoWhereCommandExpressionBuilder(ICommandTextWhereArgBuilder<T> commandTextWhereArgBuilder)
        {
            _commandTextWhereArgBuilder = commandTextWhereArgBuilder ?? throw new ArgumentNullException(nameof(commandTextWhereArgBuilder));
        }

        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType) return string.Empty;
            var binaryExpressionType = (BinaryExpression)node;
            return $"({_commandTextWhereArgBuilder.Build(binaryExpressionType.Left)}) AND ({_commandTextWhereArgBuilder.Build(binaryExpressionType.Right)})";
        }
    }

    public class SqlLiteEqualWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {
        private readonly ICommandTextWhereArgBuilder<T> _commandTextWhereArgBuilder;

        public ExpressionType ExpressionType => ExpressionType.Equal;
        public SqlLiteEqualWhereCommandExpressionBuilder(ICommandTextWhereArgBuilder<T> commandTextWhereArgBuilder)
        {
            _commandTextWhereArgBuilder = commandTextWhereArgBuilder ?? throw new ArgumentNullException(nameof(commandTextWhereArgBuilder));
        }

        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType) return string.Empty;
            var binaryExpressionType = (BinaryExpression)node;
            return $"{_commandTextWhereArgBuilder.Build(binaryExpressionType.Left)} = {_commandTextWhereArgBuilder.Build(binaryExpressionType.Right)}";
        }
    }

    public class SqliteMemberAccessWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {

        private readonly IObjectToSqlValueConverter _objectToSqlValueConverter;
        public ExpressionType ExpressionType => ExpressionType.MemberAccess;
        public SqliteMemberAccessWhereCommandExpressionBuilder(IObjectToSqlValueConverter objectToSqlValueConverter)
        {
            _objectToSqlValueConverter = objectToSqlValueConverter ?? throw new ArgumentNullException(nameof(objectToSqlValueConverter));
        }

        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType) return string.Empty;
            var memberExpression = (MemberExpression)node;
            if(memberExpression.Member.MemberType == MemberTypes.Field)
            {
                if(memberExpression.Expression == null) return "NULL UNDERLYING EXPRESSION IN MEMBER EXPRESSION";
                if (memberExpression.Expression.NodeType != ExpressionType.Constant) return "NOT A CONSTANT EXPRESSION NODE TYPE IN MEMBER EXPRESSION";
                var constantExpression = (ConstantExpression)memberExpression.Expression;
                var fieldInfo = (FieldInfo)memberExpression.Member;
                return $"\"{_objectToSqlValueConverter.Convert(fieldInfo.GetValue(constantExpression.Value))}\"";
            }
            return $"\"{memberExpression?.Member?.Name}\"";
        }
    }

    public class SqliteConvertAccessWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {

        private readonly ICommandTextWhereArgBuilder<T> _commandTextWhereArgBuilder;
        public ExpressionType ExpressionType => ExpressionType.Convert;
        public SqliteConvertAccessWhereCommandExpressionBuilder(ICommandTextWhereArgBuilder<T> commandTextWhereArgBuilder)
        {
            _commandTextWhereArgBuilder = commandTextWhereArgBuilder ?? throw new ArgumentNullException(nameof(commandTextWhereArgBuilder));
        }

        private static readonly IDictionary<Tuple<Type, Type>, string> _formatBySourceAndDestinationTupleTypes =
            new Dictionary<Tuple<Type, Type>, string>()
            {
                {Tuple.Create(typeof(int),typeof(long)), "{0}" },
            };

        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType) return string.Empty;
            var unaryExpression = (UnaryExpression)node;
            if (!_formatBySourceAndDestinationTupleTypes.TryGetValue(Tuple.Create(unaryExpression.Operand.Type, unaryExpression.Type), out var format))
                return "UNKNONW CONVERT EXPRESSION";
            return string.Format(format, _commandTextWhereArgBuilder.Build(unaryExpression.Operand));
        }
    }

    public class SqliteConstantWhereCommandExpressionBuilder<T> : ISqlLiteWhereCommandExpressionBuilder where T : class
    {
        private readonly IObjectToSqlValueConverter _objectToSqlValueConverter1;
        public ExpressionType ExpressionType => ExpressionType.Constant;
        public SqliteConstantWhereCommandExpressionBuilder(IObjectToSqlValueConverter objectToSalValueConverter)
        {
            _objectToSqlValueConverter1 = objectToSalValueConverter ?? throw new ArgumentNullException(nameof(objectToSalValueConverter));
        }

        public string Build(Expression? node)
        {
            if (node.NodeType != ExpressionType) return string.Empty;
            var constantExpression = (ConstantExpression)node;
            return _objectToSqlValueConverter1.Convert(constantExpression.Value);
        }
    }

    


    public class ObjectToSqlValueConverter : IObjectToSqlValueConverter
    {
        private readonly IDictionary<Type, Func<object, string>> _valueConversionFuncByType =
            new Dictionary<Type, Func<object, string>>()
            {
                {typeof(string), value => $"'{value}'"},
                {typeof(int), value => $"{value}"},
                {typeof(long), value => $"{value}"},
                {typeof(short), value => $"{value}"},
                {typeof(float), value => $"{value}"},
                {typeof(double), value => $"{value}"},
                {typeof(decimal), value => $"{value}"},
            };
        public string Convert(object? value)
        {
            if (value == null) return "NULL";
            var valueType = value.GetType();
            if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                var underlyingValueType = Nullable.GetUnderlyingType(valueType);
                if (underlyingValueType == null) return "UNKNOWN CASE OF NULLABLE UNDERLYING NULL VALUE";
                return Convert(System.Convert.ChangeType(value, underlyingValueType));
            }
            if((valueType.IsArray || valueType.IsGenericType && valueType.GetGenericTypeDefinition().IsAssignableTo(typeof(IEnumerable))))
            {
                var enumerable = (IEnumerable)value;
                return $"({string.Join(",", Enumerate(enumerable).Select(Convert))})";
            }
            if (!_valueConversionFuncByType.TryGetValue(valueType, out var conversionFunc)) return "UNKNOWN TYPE";
            return conversionFunc(value);
        }

        private IEnumerable<object> Enumerate(IEnumerable enumerable)
        {
            foreach(var value in enumerable)
            {
                yield return value;
            }
        }
    }
}

Jointure implementation in C#

Sometimes when you want to manage your code efficiency you may need to optimize some .Net implementation.

Jointure implementation in C# may be such a case.

Underneath you will find my proposal to implement a jointure in C# environment. This is a generic code and may be optimized in many way for your implementation needs. It use System.Linq but remediation can be done very easily to not depend on this library.

using System;
using System.Collections.Generic;
using System.Linq;

namespace org.maximilienzakowski.Jointures
{
    public static class Extensions
    {
        public static TJ[] InnerJoin<TL, TR, TK, TJ>(this IEnumerable<TL> lefts, IEnumerable<TR> rights, Func<TL, TK> onLeftKey, Func<TR, TK> onRightKey,
           Func<TL, TR, TJ> jointureBuilder)
        {
            var leftsWithKeys = lefts.Select(l => new Tuple<TL, TK>(l, onLeftKey(l)));
            var rightsWithKeys = rights.Select(r => new Tuple<TR, TK>(r, onRightKey(r)));
            var rightGroupsByKey = rightsWithKeys.GroupBy(r => r.Item2, m => m.Item1).ToDictionary(m => m.Key, m => m.ToArray());
            var defaultJoineds = new TR[] { };
            var enumerable = leftsWithKeys.SelectMany(m =>
            {
                TR[] joineds = defaultJoineds;
                if (m.Item2 != null)
                {
                    rightGroupsByKey.TryGetValue(m.Item2, out joineds);
                }
                return (joineds ?? defaultJoineds).Select(joined => jointureBuilder(m.Item1, joined));
            });
            return enumerable.ToArray();
        }
        public static Tuple<TL,TR>[] LeftJoin<TL,TR,TK>(this IEnumerable<TL> lefts, IEnumerable<TR> rights, Func<TL,TK> onLeftKey, Func<TR,TK> onRightKey)
        {
            return LeftJoin(lefts, rights, onLeftKey, onRightKey, (left, right) => new Tuple<TL, TR>(left, right), default(TR));
        }

        public static Tuple<TL,TR>[] LeftJoin<TL,TR,TK>(this IEnumerable<TL> lefts, IEnumerable<TR> rights,
            Func<TL,TK> onLeftKey, Func<TR,TK> onRightKey,TR defaultJoined)
        {
            return LeftJoin(lefts, rights, onLeftKey, onRightKey, (left, right) => new Tuple<TL, TR>(left, right), defaultJoined);
        }

        public static IList<TJ> LeftJoin<TL,TR,TK,TJ>(this IEnumerable<TL> lefts,IEnumerable<TR> rights, Func<TL,TK> onLeftKey,Func<TR,TK> onRightKey, Func<TL,TR,TJ> jointureBuilder)
        {
            return LeftJoin(lefts, rights, onLeftKey, onRightKey, jointureBuilder, default(TR));
        }

        public static TJ[] LeftJoin<TL,TR,TK,TJ>(this IEnumerable<TL> lefts, IEnumerable<TR> rights, Func<TL,TK> onLeftKey,Func<TR,TK> onRightKey, 
            Func<TL,TR,TJ> jointureBuilder, TR defaultJoined)
        {
            var leftsWithKeys = lefts.Select(l => new Tuple<TL, TK>(l, onLeftKey(l)));
            var rightsWithKeys = rights.Select(r => new Tuple<TR, TK>(r, onRightKey(r)));
            var rightGroupsByKey = rightsWithKeys.GroupBy(r => r.Item2, m => m.Item1).ToDictionary(m => m.Key, m => m.ToArray());
            var defaultJoineds = new TR[] { defaultJoined };
            var enumerable = leftsWithKeys.SelectMany(m =>
            {
                TR[] joineds = defaultJoineds;
                if(m.Item2 != null)
                {
                    rightGroupsByKey.TryGetValue(m.Item2, out joineds);
                }
                return (joineds ?? defaultJoineds).Select(joined => jointureBuilder(m.Item1, joined));
            });
            return enumerable.ToArray();
        }


        public static Tuple<TL,TR>[] FullJoin<TL,TR,TK>(this IEnumerable<TL> lefts, IList<TR> rights,
            Func<TL,TK> leftKeyFunc, Func<TR,TK> rightKeyFunc, TL defaultLeft = default(TL), TR defaultRight = default(TR))
        {
            var leftJoinedToRight =
                lefts.LeftJoin(rights, leftKeyFunc, rightKeyFunc, defaultRight).ToArray();
            var joinedRightKeys =
                new HashSet<TK>(leftJoinedToRight
                    .Where(m => m.Item2 != null)
                    .Select(m => rightKeyFunc(m.Item2)).Distinct());
            var defaultLeftAndMissingRights =
                rights
                    .Select(m => Tuple.Create(m, rightKeyFunc(m)))
                    .Where(m => !joinedRightKeys.Contains(m.Item2))
                    .Select(m => Tuple.Create(defaultLeft, m.Item1)).ToArray();
            var fullJointure = leftJoinedToRight.Concat(defaultLeftAndMissingRights).ToArray();
            return fullJointure;
        }
    }
}