4
\$\begingroup\$

I'm developing a web application for simulating electronic circuits and I'm trying to add backward propagation options (guess you can immagine why). During refactoring I came up with a base class for logic operations and wanted to ask for some feedback (whatever pops up in your mind) on it. The class code is the following:

public static class LogicOperationUtilities
{
    private static readonly int _numberOfLogicalOperators = Enum.GetValues(typeof(LogicValue)).Length;
    /// <summary>
    /// Get the number of logical elements.
    /// </summary>
    public static int NumberOfLogicalElements
    {
        get { return _numberOfLogicalOperators; }
    }

    public static Func<LogicValue, LogicValue, LogicValue[][], int, LogicValue> ForwardBinaryOperation
    {
        get
        {
            return (x, y, truthTable, numberOfElements) =>
            {
                int numberOfLogicalElements = NumberOfLogicalElements;
                // this works only if assignment of _combinations is done in this way:
                //    _combinations = new LogicalValue[][]
                //    {
                //        new LogicalValue[] { LogicalValue.Unknown, LogicalValue.Unknown, Result },
                //        new LogicalValue[] { LogicalValue.Unknown, LogicalValue.False,   Result },
                //        new LogicalValue[] { LogicalValue.Unknown, LogicalValue.True,    Result },
                //        new LogicalValue[] { LogicalValue.False,   LogicalValue.Unknown, Result },
                //        new LogicalValue[] { LogicalValue.False,   LogicalValue.False,   Result },
                //        new LogicalValue[] { LogicalValue.False,   LogicalValue.True,    Result },
                //        new LogicalValue[] { LogicalValue.True,    LogicalValue.Unknown, Result },
                //        new LogicalValue[] { LogicalValue.True,    LogicalValue.False,   Result },
                //        new LogicalValue[] { LogicalValue.True,    LogicalValue.True,    Result }
                //    };
                int rowNumber = (numberOfLogicalElements * (int)x) + (int)y;

                return truthTable[rowNumber][numberOfLogicalElements - 1];
            };
        }
    }

    public static Func<LogicValue, LogicValue[][], int, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardBinaryOperation
    {
        get
        {
            return (x, truthTable, numberOfElements) 
                    => truthTable.Where(combination => combination[numberOfElements - 1] == x)
                                 .Select(combination => new Tuple<LogicValue, LogicValue>(combination[0], combination[1]));
        }
    }

    // Not operation
    //
    public static LogicValue[][] NotOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown,  LogicValue.Unknown},
                new LogicValue[] { LogicValue.False,    LogicValue.True},
                new LogicValue[] { LogicValue.True,     LogicValue.False}
            };
        }
    }
    private static readonly int _notOperationNumberOfCombinations = NotOperationTruthTable.Length;
    private static readonly int _notOperationNumberOfElements = NotOperationTruthTable[0].Length;

    /// <summary>
    /// Get the function that calculates the result of the Not operation.
    /// </summary>
    public static Func<LogicValue, LogicValue> ForwardNotOperation
    {
        get { return (x) => NotOperationTruthTable[(byte)x][1]; }
    }

    /// <summary>
    /// Get the function that calculates the input values that give a certain specified output.
    /// </summary>
    public static Func<LogicValue, IEnumerable<LogicValue>> BackwardNotOperation
    {
        get
        {
            return (x) => NotOperationTruthTable.Where(combination => combination[1] == x)
                                        .Select(combination => combination[0]);
        }
    }

    // AND operation
    //
    /// <summary>
    /// The truth table for the logical AND operation
    /// </summary>
    public static LogicValue[][] AndOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown, LogicValue.False,   LogicValue.False },
                new LogicValue[] { LogicValue.Unknown, LogicValue.True,    LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,   LogicValue.Unknown, LogicValue.False },
                new LogicValue[] { LogicValue.False,   LogicValue.False,   LogicValue.False },
                new LogicValue[] { LogicValue.False,   LogicValue.True,    LogicValue.False },
                new LogicValue[] { LogicValue.True,    LogicValue.Unknown, LogicValue.Unknown },
                new LogicValue[] { LogicValue.True,    LogicValue.False,   LogicValue.False },
                new LogicValue[] { LogicValue.True,    LogicValue.True,    LogicValue.True }
            };
        }
    }
    private static readonly int _andOperationNumberOfCombinations = AndOperationTruthTable.Length;
    private static readonly int _andOperationNumberOfElements = AndOperationTruthTable[0].Length;

    public static Func<LogicValue, LogicValue, LogicValue> ForwardAndOperation
    {
        get
        {
            return (x, y) => ForwardBinaryOperation(x, y, AndOperationTruthTable, _andOperationNumberOfElements);
        }
    }

    public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardAndOperation
    {
        get
        {
            return (x) => BackwardBinaryOperation(x, AndOperationTruthTable, _andOperationNumberOfElements);
        }
    }

    // NAND operation
    //
    public static LogicValue[][] NandOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown, LogicValue.Unknown, LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown, LogicValue.False,   LogicValue.True },
                new LogicValue[] { LogicValue.Unknown, LogicValue.True,    LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,   LogicValue.Unknown, LogicValue.True },
                new LogicValue[] { LogicValue.False,   LogicValue.False,   LogicValue.True },
                new LogicValue[] { LogicValue.False,   LogicValue.True,    LogicValue.True },
                new LogicValue[] { LogicValue.True,    LogicValue.Unknown, LogicValue.Unknown },
                new LogicValue[] { LogicValue.True,    LogicValue.False,   LogicValue.True },
                new LogicValue[] { LogicValue.True,    LogicValue.True,    LogicValue.False }
            };
        }
    }
    private static readonly int _nandOperationNumberOfCombinations = NandOperationTruthTable.Length;
    private static readonly int _nandOperationNumberOfElements = NandOperationTruthTable[0].Length;

    public static Func<LogicValue, LogicValue, LogicValue> ForwardNandOperation
    {
        get
        {
            return (x, y) => ForwardBinaryOperation(x, y, NandOperationTruthTable, _nandOperationNumberOfElements);
        }
    }

    public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardNandOperation
    {
        get { return (x) => BackwardBinaryOperation(x, NandOperationTruthTable, _nandOperationNumberOfElements); }
    }

    // OR operation
    //
    public static LogicValue[][] OrOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown,  LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.False,     LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.True,      LogicValue.True },
                new LogicValue[] { LogicValue.False,    LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,    LogicValue.False,     LogicValue.False },
                new LogicValue[] { LogicValue.False,    LogicValue.True,      LogicValue.True },
                new LogicValue[] { LogicValue.True,     LogicValue.Unknown,   LogicValue.True },
                new LogicValue[] { LogicValue.True,     LogicValue.False,     LogicValue.True },
                new LogicValue[] { LogicValue.True,     LogicValue.True,      LogicValue.True }
            };
        }
    }
    private static readonly int _orOperationNumberOfCombinations = OrOperationTruthTable.Length;
    private static readonly int _orOperationNumberOfElements = OrOperationTruthTable[0].Length;

    public static Func<LogicValue, LogicValue, LogicValue> ForwardOrOperation
    {
        get
        {
            return (x, y) => ForwardBinaryOperation(x, y, OrOperationTruthTable, _orOperationNumberOfElements);
        }
    }

    public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardOrOperation
    {
        get { return (x) => BackwardBinaryOperation(x, OrOperationTruthTable, _orOperationNumberOfElements); }
    }

    // NOR operation
    //
    public static LogicValue[][] NorOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown,  LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.False,     LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.True,      LogicValue.False },
                new LogicValue[] { LogicValue.False,    LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,    LogicValue.False,     LogicValue.True },
                new LogicValue[] { LogicValue.False,    LogicValue.True,      LogicValue.False },
                new LogicValue[] { LogicValue.True,     LogicValue.Unknown,   LogicValue.False },
                new LogicValue[] { LogicValue.True,     LogicValue.False,     LogicValue.False },
                new LogicValue[] { LogicValue.True,     LogicValue.True,      LogicValue.False }
            };
        }
    }
    private static readonly int _norOperationNumberOfCombinations = NorOperationTruthTable.Length;
    private static readonly int _norOperationNumberOfElements = NorOperationTruthTable[0].Length;

    public static Func<LogicValue, LogicValue, LogicValue> ForwardNorOperation
    {
        get
        {
            return (x, y) => ForwardBinaryOperation(x, y, NorOperationTruthTable, _norOperationNumberOfElements);
        }
    }

    public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardNorOperation
    {
        get { return (x) => BackwardBinaryOperation(x, NorOperationTruthTable, _norOperationNumberOfElements); }
    }

    // XOR operation
    //
    public static LogicValue[][] XorOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown,  LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.False,     LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.True,      LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,    LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,    LogicValue.False,     LogicValue.False },
                new LogicValue[] { LogicValue.False,    LogicValue.True,      LogicValue.True },
                new LogicValue[] { LogicValue.True,     LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.True,     LogicValue.False,     LogicValue.True },
                new LogicValue[] { LogicValue.True,     LogicValue.True,      LogicValue.False }
            };
        }
    }
    private static readonly int _xorOperationNumberOfCombinations = XorOperationTruthTable.Length;
    private static readonly int _xorOperationNumberOfElements = XorOperationTruthTable[0].Length;

    public static Func<LogicValue, LogicValue, LogicValue> ForwardXorOperation
    {
        get
        {
            return (x, y) => ForwardBinaryOperation(x, y, XorOperationTruthTable, _xorOperationNumberOfElements);
        }
    }

    public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardXorOperation
    {
        get
        {
            return (x) => BackwardBinaryOperation(x, XorOperationTruthTable, _xorOperationNumberOfElements);
        }
    }

    // NXOR operation
    //
    public static LogicValue[][] NxorOperationTruthTable
    {
        get
        {
            return new LogicValue[][]
            {
                new LogicValue[] { LogicValue.Unknown,  LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.False,     LogicValue.Unknown },
                new LogicValue[] { LogicValue.Unknown,  LogicValue.True,      LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,    LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.False,    LogicValue.False,     LogicValue.True },
                new LogicValue[] { LogicValue.False,    LogicValue.True,      LogicValue.False },
                new LogicValue[] { LogicValue.True,     LogicValue.Unknown,   LogicValue.Unknown },
                new LogicValue[] { LogicValue.True,     LogicValue.False,     LogicValue.False },
                new LogicValue[] { LogicValue.True,     LogicValue.True,      LogicValue.True }
            };
        }
    }
    private static readonly int _nxorOperationNumberOfCombinations = NxorOperationTruthTable.Length;
    private static readonly int _nxorOperationNumberOfElements = NxorOperationTruthTable[0].Length;

    public static Func<LogicValue, LogicValue, LogicValue> ForwardNxorOperation
    {
        get
        {
            return (x, y) => ForwardBinaryOperation(x, y, NxorOperationTruthTable, _nxorOperationNumberOfElements);
        }
    }

    public static Func<LogicValue, IEnumerable<Tuple<LogicValue, LogicValue>>> BackwardNxorOperation
    {
        get
        {
            return (x) => BackwardBinaryOperation(x, NxorOperationTruthTable, _nxorOperationNumberOfElements);
        }
    }
}

This class is used in the following way in a second class which should be the class that is used from the various gates:

public class TruthTable
{
    private readonly LogicValue[][] _truthTable;

    /// <summary>
    /// The types of logic operations.
    /// </summary>
    public enum LogicOperation
    {
        NOT,
        AND,
        OR,
        XOR,
        NAND,
        NOR,
        NXOR
    }

    /// <summary>
    /// Get the logic operation this TruthTable instance refers to.
    /// </summary>
    public LogicOperation Operation { get; private set; }

    /// <summary>
    /// Get a flag indicating if the truth table is of a 
    /// binary operation or not.
    /// </summary>
    public bool IsBinaryOperation
    {
        get { return Operation != LogicOperation.NOT; }
    }

    /// <summary>
    /// Get the truth table (as a matrix) for the specified
    /// logic operation.
    /// </summary>
    public LogicValue[,] OperationTruthTable
    {
        get
        {
            int numberOfCombinations = _truthTable.Length;
            int numberOfElements = _truthTable[0].Length;
            var result = new LogicValue[numberOfCombinations, numberOfElements];

            for (int i = 0; i < numberOfCombinations; i++)
            {
                for (int j = 0; j < numberOfElements; j++)
                {
                    result[i, j] = _truthTable[i][j];
                }
            }

            return result;
        }
    }

    /// <summary>
    /// Get the result value of a unary logic operation with
    /// input x.
    /// </summary>
    /// <returns>
    /// The result of the unary operation with input x.
    /// </returns>
    public LogicValue this[LogicValue x]
    {
        get
        {
            if (IsBinaryOperation)
            {
                throw new InvalidOperationException(
                    "This operation is valid only for unary operations.");
            }

            return _truthTable[(int)x][1];
        }
    }

    /// <summary>
    /// Get the result value of a binary logic operation with
    /// input x and y.
    /// </summary>
    /// <returns>
    /// The result of the binary operation with input x and y.
    /// </returns>
    public LogicValue this[LogicValue x, LogicValue y]
    {
        get
        {
            if (!IsBinaryOperation)
            {
                throw new InvalidOperationException(
                    "This operation is valid only for binary operations.");
            }

            return _truthTable[3 * (int)x + (int)y][2];
        }
    }

    /// <summary>
    /// Constructor of a TruthTable instance.
    /// </summary>
    /// <param name="logicOperation">
    /// The logic operation this truth table refers to.
    /// </param>
    public TruthTable(LogicOperation logicOperation)
    {
        switch (logicOperation)
        {
            case LogicOperation.AND:
                _truthTable = LogicOperationUtilities.AndOperationTruthTable;
                break;
            case LogicOperation.NAND:
                _truthTable = LogicOperationUtilities.NandOperationTruthTable;
                break;
            case LogicOperation.NOR:
                _truthTable = LogicOperationUtilities.NorOperationTruthTable;
                break;
            case LogicOperation.NOT:
                _truthTable = LogicOperationUtilities.NotOperationTruthTable;
                break;
            case LogicOperation.NXOR:
                _truthTable = LogicOperationUtilities.NxorOperationTruthTable;
                break;
            case LogicOperation.OR:
                _truthTable = LogicOperationUtilities.OrOperationTruthTable;
                break;
            case LogicOperation.XOR:
                _truthTable = LogicOperationUtilities.XorOperationTruthTable;
                break;
        }

        Operation = logicOperation;
    }
}

Just for completing the scenario, LogicValue is an enum defined in the following way:

public enum LogicValue : byte
{
    Unknown = 0,
    False = 1,
    True = 2
}

For the testing part (for which I'd appreciate some feedback also) I used the following:

LogicOperationTests.cs

[TestClass]
public class LogicOperationTests
{
    [TestMethod]
    public void ForwardNotOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNotOperation(LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardNotOperation(LogicValue.False));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardNotOperation(LogicValue.True));
    }

    [TestMethod]
    public void BackwardNotOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardNotOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardNotOperation(logicalValue));
            }
        }
    }

    [TestMethod]
    public void ForwardAndOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardAndOperation(LogicValue.Unknown, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardAndOperation(LogicValue.Unknown, LogicValue.False));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardAndOperation(LogicValue.Unknown, LogicValue.True));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardAndOperation(LogicValue.False, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardAndOperation(LogicValue.False, LogicValue.False));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardAndOperation(LogicValue.False, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardAndOperation(LogicValue.True, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardAndOperation(LogicValue.True, LogicValue.False));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardAndOperation(LogicValue.True, LogicValue.True));
    }

    [TestMethod]
    public void BackwardAndOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardAndOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardAndOperation(logicalValue.Item1, logicalValue.Item2));
            }
        }
    }

    [TestMethod]
    public void ForwardNandOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNandOperation(LogicValue.Unknown, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardNandOperation(LogicValue.Unknown, LogicValue.False));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNandOperation(LogicValue.Unknown, LogicValue.True));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardNandOperation(LogicValue.False, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardNandOperation(LogicValue.False, LogicValue.False));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardNandOperation(LogicValue.False, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNandOperation(LogicValue.True, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True,    LogicOperationUtilities.ForwardNandOperation(LogicValue.True, LogicValue.False));
        Assert.AreEqual(LogicValue.False,   LogicOperationUtilities.ForwardNandOperation(LogicValue.True, LogicValue.True));
    }

    [TestMethod]
    public void BackwardNandOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardNandOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardNandOperation(logicalValue.Item1, logicalValue.Item2));
            }
        }
    }

    [TestMethod]
    public void ForwardOrOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardOrOperation(LogicValue.Unknown, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardOrOperation(LogicValue.Unknown, LogicValue.False));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.Unknown, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardOrOperation(LogicValue.False, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardOrOperation(LogicValue.False, LogicValue.False));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.False, LogicValue.True));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.True, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.True, LogicValue.False));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardOrOperation(LogicValue.True, LogicValue.True));
    }

    [TestMethod]
    public void BackwardOrOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardOrOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardOrOperation(logicalValue.Item1, logicalValue.Item2));
            }
        }
    }

    [TestMethod]
    public void ForwardNorOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNorOperation(LogicValue.Unknown, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNorOperation(LogicValue.Unknown, LogicValue.False));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.Unknown, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNorOperation(LogicValue.False, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNorOperation(LogicValue.False, LogicValue.False));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.False, LogicValue.True));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.True, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.True, LogicValue.False));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNorOperation(LogicValue.True, LogicValue.True));
    }

    [TestMethod]
    public void BackwardNorOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardNorOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardNorOperation(logicalValue.Item1, logicalValue.Item2));
            }
        }
    }

    [TestMethod]
    public void ForwardXorOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.Unknown, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.Unknown, LogicValue.False));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.Unknown, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.False, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardXorOperation(LogicValue.False, LogicValue.False));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardXorOperation(LogicValue.False, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardXorOperation(LogicValue.True, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardXorOperation(LogicValue.True, LogicValue.False));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardXorOperation(LogicValue.True, LogicValue.True));
    }

    [TestMethod]
    public void BackwardXorOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardXorOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardXorOperation(logicalValue.Item1, logicalValue.Item2));
            }
        }
    }

    [TestMethod]
    public void ForwardNxorOperationTest()
    {
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.Unknown, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.Unknown, LogicValue.False));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.Unknown, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.False, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNxorOperation(LogicValue.False, LogicValue.False));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNxorOperation(LogicValue.False, LogicValue.True));
        Assert.AreEqual(LogicValue.Unknown, LogicOperationUtilities.ForwardNxorOperation(LogicValue.True, LogicValue.Unknown));
        Assert.AreEqual(LogicValue.False, LogicOperationUtilities.ForwardNxorOperation(LogicValue.True, LogicValue.False));
        Assert.AreEqual(LogicValue.True, LogicOperationUtilities.ForwardNxorOperation(LogicValue.True, LogicValue.True));
    }

    [TestMethod]
    public void BackwardNxorOperationTest()
    {
        foreach (LogicValue value in Enum.GetValues(typeof(LogicValue)))
        {
            foreach (var logicalValue in LogicOperationUtilities.BackwardNxorOperation(value))
            {
                Assert.AreEqual(value, LogicOperationUtilities.ForwardNxorOperation(logicalValue.Item1, logicalValue.Item2));
            }
        }
    }
}

and:

TruthTableTests.cs

[TestClass]
public class TruthTableTests
{
    [TestMethod]
    public void AndTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.AND);

        Assert.IsTrue(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.AndOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.AndOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    public void NandTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.NAND);

        Assert.IsTrue(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NandOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NandOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    public void OrTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.OR);

        Assert.IsTrue(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.OrOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.OrOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    public void NorTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.NOR);

        Assert.IsTrue(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NorOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NorOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    public void NotTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.NOT);

        Assert.IsFalse(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NotOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NotOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    public void XorTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.XOR);

        Assert.IsTrue(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.XorOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.XorOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    public void NxorTruthTableCreationTest()
    {
        TruthTable table = new TruthTable(TruthTable.LogicOperation.NXOR);

        Assert.IsTrue(table.IsBinaryOperation);
        Assert.IsTrue(HaveSameSize(LogicOperationUtilities.NxorOperationTruthTable, table.OperationTruthTable));
        Assert.IsTrue(ContainSameElements(LogicOperationUtilities.NxorOperationTruthTable, table.OperationTruthTable));
    }

    [TestMethod]
    [ExpectedException(typeof(InvalidOperationException))]
    public void IndexOperatorTest()
    {
        TruthTable unary = new TruthTable(TruthTable.LogicOperation.NOT);
        TruthTable binary = new TruthTable(TruthTable.LogicOperation.AND);
        var values = Enum.GetValues(typeof(LogicValue));

        var shouldFail = unary[LogicValue.False, LogicValue.False];
        var shouldAlsoFail = binary[LogicValue.False];

        foreach (LogicValue x in values)
        {
            Assert.AreSame(unary[x], LogicOperationUtilities.ForwardNotOperation(x));

            foreach (LogicValue y in values)
            {
                Assert.AreSame(binary[x, y], LogicOperationUtilities.ForwardAndOperation(x, y));
            }
        }
    }

    public bool HaveSameSize(LogicValue[][] truthTable, LogicValue[,] truthTableMatrix)
    {
        if (truthTable.Length != truthTableMatrix.GetLength(0))
        {
            return false;
        }

        var numberOfColumns = truthTableMatrix.GetLength(1);
        for (int i = 0; i < truthTable.Length; i++)
        {
            if (truthTable[i].Length != numberOfColumns)
            {
                return false;
            }
        }

        return true;
    }

    public bool ContainSameElements(LogicValue[][] truthTable, LogicValue[,] truthTableMatrix)
    {
        var rows = truthTable.Length;
        var cols = truthTableMatrix.GetLength(1);

        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                if (truthTable[i][j] != truthTableMatrix[i, j])
                {
                    return false;
                }
            }
        }

        return true;
    }
}

As usual, I'm open to any type of feedback, even though for now I'm concentrating more on functionality and readability rather than performance.

Let me know if you have any questions.

\$\endgroup\$
  • 3
    \$\begingroup\$ Use Tuple.Create(...) instead of new Tuple<T,U>(...) as it is more idiomatic and it allows for generic parameter inference. \$\endgroup\$ – Jesse C. Slicer Nov 23 '15 at 16:10
  • 3
    \$\begingroup\$ It's unclear from this code whether you really need any of this. Truth tables are just functions with a specific input and output type. Why do they need to be represented as an enum value and a jagged array and a class? \$\endgroup\$ – Ben Aaronson Nov 23 '15 at 17:15
13
\$\begingroup\$

Do not use (if possible) static classes. You save few bytes and few CPU cycles but they're harder to test if you need to mock their implementation. It's a general rule then it may allow exceptions (not in this case, IMO).

Do not return Func<LogicValue, LogicValue, LogicValue[][], int, LogicValue> from a method (or property). It's far too complex to be clear. Think you have a function with four parameters (one of which is a jagged array) and you need to understand which is what at calling point.

Keep (as much as possible) coherent names: you have a property NumberOfLogicalElements that returns _numberOfLogicalOperators. Unless you have a very good reason you should pick one name and use that one everywhere. Are they elements or operators? Is there any difference? Do they need to be different? If yes then name should make clear why.

In, for example, ForwardBinaryOperation you do not need a local variable named numberOfLogicalElements. Drop it, JIT compiler will inline property access. Usually you should avoid such micro-optimizations.

In the same (static) class you have properties to do different things, ForwardBinaryOperation and NotOperationTruthTable. I'd move them to separate classes (but see later).

Your enum values are upper-case, follow common casing convention: AND is not an acronym and even in that case it shouldn't be coded in upper-case, name it And.

The main point (IMO) is that LogicOperationUtilities is an utility class. Call it utility or helper but it always smells bad. Many times (not always) when you need such class you are, in reality, doing something bad elsewhere.

In your case I'd simply drop LogicOperationUtilities class and LogicOperation enum and I'd use inheritance to do same job.

Make LogicOperation an abstract base class. Speaking in domain language you may say something like this: "Each logic operation has a variable number of inputs and a truth table to map each combination to one (or more) output value(s)".

Peak keywords from this to build your domain model: LogicValue to represent a logical state, Input to represent a single input for a LogicOperation and Output to represent a single output. Because each operation may have multiple inputs/outputs you will also need InputCollection and OutputCollection. Because one output may be connected to an input you may abstract type of Input and Output into a generic Port class. Note that you have a LogicValue enumeration but you already have same type built-in in .NET, plus all C# syntactic sugar: drop LogicValue in favor of bool?.

You will then add more: "Each port has a name and each logic operation may refer to its inputs/outputs using that name or their - arbitrary - indices". Going in the right direction? Now let's say that "Each input port can receive a value, input ports from outside and output ports from a logic operation".

Now you know you have to define a Port class (with a Name and a Value property) and two derived classes: InputPort and OutputPort. Each LogicOperation class will have a property Inputs (of type InputCollection, an almost empty Collection<InputPort>) for its inputs and an Output property (of type OutputCollection, an almost empty Collection<OutputPort>).

Few more details: "Must be available these binary operations: And, Or, Nand, Nor, Xor and Nxor and one unary operator Not".

Now you know you have two main classes (that define how many inputs/outputs you have). Define an abstract BinaryLogicOperation and another abstract UnaryLogicOperation. Concrete classes (And, Or and so on) will derive from them.

Note we moved responsability from TruthTable (a God class and its utility class) to the proper place: behavior is defined in each class and you're using inheritance to hide it.

What's then a truth-table? TruthTable is then simply a combination of inputs (InputCollection) associated with a LogicOperation. Its output column is calculated. Note that in this way you may build simple digital logic simulator simply connecting inputs and outputs and C# events will do the job, see this Proof of Concept:

abstract class Port {
    public bool? Value {
        get { return _value; }
        set {
            if (_value != value) {
                _value = value;
                OnValueChanged(EventArgs.Empty);
            }
        }

        public event EventHandler ValueChanged;

        protected virtual void OnValueChanged(EventArgs e) {
            var valueChanged = ValueChanged;
            if (valueChanged != null) {
                valueChanged(this, e);
            }
        }
    }

    private bool? _value;
}

Over this you then should add INotifyPropertyChanged implementation and, possibly, IObservable for example to better integrate with Reactive Extensions. To code/build/design/load your digital network simulation will be as easy as attaching few events. I'd also suggest to take a look to Task Parallel Library for a different approach to this problem (they have different usage scenarios but it's greatly off-topic to discuss them here).


You may have noted I almost didn't write any code, you already have a lot of it but what I tried to express is the logical flow, code is merely a detail here.


One thing about testing. You're exposing an implementation detail (your utility class) just to test it. In my opinion tests should check only public interface of your components. Testing public interface all the implementation details should be involved (and code coverage should quickly confirm this). They shouldn't check private implementation details because they're free to change and what you want to ensure is valid is component behavior, not how it's implemented. If you can't test it unless you test your implementation then there is something wrong (or you have too convoluted and/or useless code). BTW an isolated domain model will also help you in tests.

\$\endgroup\$
  • \$\begingroup\$ Can you please explain further what you mean by number 3? \$\endgroup\$ – 410_Gone Nov 23 '15 at 17:10
  • 1
    \$\begingroup\$ A property NumberOfLogicalElements returns value of a field numberOfLogicalOperators. Elements or Operators but I'd pick one _definition for that thing and I'd use it extensively (or in future I may ask myself if I really meant something different, in that case I'd use more clear and different names). \$\endgroup\$ – Adriano Repetti Nov 23 '15 at 17:13
  • \$\begingroup\$ Lots of useful feedback in this answer. I totally ignored the possibility of utility classes being a code smell and the domain model was not well built. Thanks a lot \$\endgroup\$ – Gentian Kasa Nov 24 '15 at 9:05
  • \$\begingroup\$ @GentianKasa utility classes are not always code smell, honestly I'm not so pure about this but often some refactoring (from top) will wipe them out. You welcome, I didn't write much code, I wanted to outline one possible different approach (separately from other suggestions). \$\endgroup\$ – Adriano Repetti Nov 24 '15 at 9:10
  • 1
    \$\begingroup\$ I don't care that much about the presence of code, that can always be written afterwards. It's the design and testing logic that I loved the most from the answer ;) \$\endgroup\$ – Gentian Kasa Nov 24 '15 at 9:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.