Link to the first partLink to the second partContent and container commands
Some commands involve working with files originally stored on the package developer’s computer. It is clear that these files need to be delivered along with the package (and preferably, right inside the package) to the package consumer. Let's try to begin to imagine how this will work.
We have an instance of the PackageBuilder class, to which when constructing we specify the PackageBody argument, which contains, among other things, the Command command, which is the root node of the package command tree. The SaveResult () method of an instance of the PackageBuilder class should recursively traverse the entire tree, and for those commands that use content files located on the developer's computer, include the contents of all these files in the package body. It should also include an xml file in the package body into which PackageBody itself will be serialized with a complete description of the package and the commands it performs.
How can the SaveResult () method, when recursively traversing the command tree, find out if the current command contains files that need to be included in the package? If we make it a simple check of the type of such pseudocode: “If the current command is CreateFile, then we extract the SourceFile file from it”, then we get the fact that the developer of extensions for the installer, without having access to the source code of the PackageBuilder class, can no longer add here another check for your new content team. Here we could gain the creation of another abstract class like ContentCommand, from which we would inherit, but it is not clear where in the command hierarchy we would embed this class? At the moment, the CreateFile command is inherited from TransactionalCommand, and CreateFile implies that it is also a content command. But WriteRegistry is also inherited from TransactionalCommand, however it cannot be content, because it simply writes the value to the registry with a certain key. By the way, the WriteRegistryFileCommand team is also a TransactionalCommand, and at the same time it is also a content one, because it requires * .reg, a file that describes all registry keys to be modified.
Thus, it is not exactly clear where the ContentCommand class can be embedded in the hierarchy, and since you cannot use impurity classes in C #, as in C ++ (source), we will solve this issue by introducing the new IContentCommand interface that we will implement in each content team . Fortunately, each specific class can implement any number of interfaces, which is good for us.
The IContentCommand interface in the first approximation is as follows:
public interface IContentCommand { string ContentPath { get; set; } }
Please note that we provide both a getter and a setter for a single interface element. We need the setter to replace the initial value of the full path to the file on the package developer’s machine with the short file name inside the finished package. Thus, on the consumer's machine package, the team will extract the content files from the package by their short name inside the package. And this is logical, the consumer does not have access to the developer’s machine, which means he doesn’t have to know the full paths to the source files, and indeed the file system structure on the developer’s machine.
')
The implementation of this interface for each content team is simple and obvious. For example, for the CreateFile command, it will be:
public string ContentPath { get { return SourcePath; } set { SourcePath = value; } }
Similarly, it will look for any other content team.
The only drawback of the IContentCommand interface is that it operates on one single file path, that is, with one file or one folder. And what if the command contains pointers to two or more files or folders? Then the interface will look like this:
interface IMultipleContentCommand { IEnumerable<ContentPath> ContentPaths { get; } }
Now, instead of a simple string, I had to return a collection of objects of type ContentPath that support writing and reading to the specified property of the object owner. I could not think of any other options besides using type reflection, and therefore I implemented the ContentPath type as follows:
public class ContentPath { private Command _command; private PropertyInfo _property; public ContentPath(Command command, PropertyInfo property) { if (!(command is IMultipleContentCommand)) { throw new ArgumentException("Must be the IMultipleContentCommand"); } _command = command; _property = property; } public string Name { get { return _property.Name; } } public string Value { get { return (string)_property.GetValue(_command, null); } set { _property.SetValue(_command, value, null); } } }
Here, I just showed the possibility of creating multi-content commands, but so far I can not imagine a single command that requires such functionality (I am going to attribute teams with folders to ordinary content commands that operate with one single path). Therefore, the IMultipleContentCommand interface will remain only in drafts, I am going to accept the decision on its use later, without complicating the application class interfaces unnecessarily.
As we remember, we use an instance of the PackageBuilder class to generate an installation package file. Until now, he was responsible only for generating an XML file containing the package body with the command tree included in it. After we realized the need to store in the package not only the XML file, but also the rest of the content files, we need to revise the algorithm of the PackageBuilder class. Now he is responsible for the following things:
- Generating an XML file containing package commands
- Inclusion in the package of all content files originally stored on the package developer’s machine
- Combining an array of files into one archive file.
In order not to lay my implementation of a specific type of archive (zip, rar, tar, or something else) in my implementation, I encapsulate this behavior in a separate PackageContent class.

As you can see, PackageBuilder now knows nothing about the methods for generating the resulting package, that is, about how exactly one is created from many disparate files. You will also need to ensure that the Package class does not rely on this knowledge at runtime, but uses the open methods of the PackageBuilder class. Thus, we provide complete encapsulation of the package-unpacking of packages, and we leave no chance for user classes to learn about the internal implementation of this algorithm. Let the PackageContent class have two constructors: the first creates an empty package for the needs of the PackageBuilder, the second constructor receives the path to the package file as input, and creates an instance already filled with content for the needs of the Package instance and its Execute () method.

In my case, I suggest using tar archiving as the easiest way to package files. I'm not going to go into the details of this method, since the meaning of the article is not in these details.
I will note the following. When forming a package, it is possible that the same content file is used in several different commands. In order not to keep its identical copies inside the package, it is necessary for PackageContent to keep a list of all files uploaded to it, and to always answer the question - is there already this file inside of me? This check will be made according to the similarity of the absolute path to the file. And one more note. It is also possible that inside the package two different files are added from different folders, but with the same name. Since the package hierarchy is unlikely to exist (or rather, it may or may not exist) file hierarchy, you should save files inside the package under the newly generated names, it is desirable to generate them using the Guid class, so surely the names of the files inside the package are not matched. Accordingly, after renaming a file and saving it inside a package under a new name, you need to change the original path in the corresponding command to a new relative path.
In order for PackageBuilder to figure out whether the current command has commands embedded in it, we’ll make sure that all commands (let's call them containers) containing other commands can report this to the calling code. To do this, they will implement the IContainerCommand interface, here is its description:
public interface IContainerCommand { IEnumerable<Command> InnerCommands { get; } }
Implementing this interface for container commands will be trivial. For example, CommandSequence will simply return its Commands property:
public IEnumerable<Command> InnerCommands { get { return Commands; } }
And ExecuteIf will return a list consisting of one Command:
public IEnumerable<Command> InnerCommands { get { return new List<Command>() { Command }; } }
I will now give the code for the SaveResult and ProcessIncludedFiles methods for an accurate understanding of how it all works:
public const string MAIN_PACKAGE_FILENAME = "package.xml"; public const string PACKAGE_FILE_EXTENSION = ".gin"; public const string PACKAGE_CONTENT_FILE_EXTENSION = ".cnt"; public void SaveResult(string filePath) { ProcessIncludedFiles(_body.Command); string xmlFilePath = GinSerializers.PackageBodySerializer.Serialize(_body); _content.AddContent(xmlFilePath, MAIN_PACKAGE_FILENAME); _content.SaveAs(filePath); } private void ProcessIncludedFiles(Command command) { if (command is IContainerCommand) { IContainerCommand iContainer = (IContainerCommand)command; foreach (Command cmd in iContainer.InnerCommands) { ProcessIncludedFiles(cmd); } } if (command is IContentCommand) { IContentCommand cntCommand = (IContentCommand)command; string sourceFilePath = cntCommand.ContentPath; if (!_content.ContainFilePath(sourceFilePath)) { string destFileName = GetGuidContentFileName(); _content.AddContent(sourceFilePath, destFileName); cntCommand.ContentPath = destFileName; } else { string destFileName = _content.GetFileName(sourceFilePath); cntCommand.ContentPath = destFileName; } } } private string GetGuidContentFileName() { return Guid.NewGuid().ToString("N") + PACKAGE_CONTENT_FILE_EXTENSION; }
Just above, I have already described in detail the operation of this code.
Comparison operators
I considered string comparison operators in the first part. Here I will try to implement comparison operators for numeric data. As we remember, the ExecuteIf command requires for its execution the presence in the execution context (ExecutionContext) of a Boolean variable, which is the result of the comparison of two operands. These operands can be of various types (int, double, etc.). The comparison operator itself, in turn, can also be different (LessThan, GreaterThan, Equal, etc), and, as you can see, not all the set of operators can be applied to each specific type of operand, for example, the StartsWith operator is hardly worth applying to integer numbers. As you can see, the creation of a team that implements the comparison of operands is a very nontrivial task, especially considering our desire to design the team so that the end user programmer can expand the basic set of comparison operators with their own operators.
The first, most obvious, approach to implementing comparison operators looks like a command with arguments:
string FirstOperandName,
string SecondOperandName,
CompareOperation Operation.
For integers it looks like this:
public enum CompareOperation { Equals, GreaterThan } public class StringCompareCommand : Command { public string FirstOperandName { get; set; } public string SecondOperandName { get; set; } public CompareOperation Operation { get; set; } private ExecutionContext _context; public StringCompareCommand(ExecutionContext context) { _context = context; } public override void Do() { bool compareResult = false; int firstOperand = (int)_context.GetResult(FirstOperandName); int secondOperand = (int)_context.GetResult(SecondOperandName); switch (Operation) { case CompareOperation.Equals: compareResult = firstOperand == secondOperand; break; case CompareOperation.GreaterThan: compareResult = firstOperand < secondOperand; break; } _context.SaveResult(ResultName, compareResult); } }
Here I have described only two comparison operators, and only for integers. Accordingly, if we want to compare decimal (decimal) numbers, we will add the DecimalCompareCommand class inheriting from the Command, almost completely repeating the IntCompareCommand class, except for the type conversion (decimal). And if we want to add the LessThan statement to them, we will have to make changes to the two classes IntCompareCommand, DecimalCompareCommand in the switch statement of the Do () method.
What I don't like about this approach? A huge number of duplicate patterns - the code of each class is generally almost identical. The inability of a third-party developer to add new comparison operators to the CompareOperation enumeration.
I think that the switch statement should be replaced by the use of polymorphism. Let's try to bring it to life.
Since in our task two aspects can change over time - the type of operands and the type of operation, we should encapsulate each of these aspects into a separate abstract class. Let them be called CompareCommand and CompareOperand.
public abstract class CompareCommand: Command { public string FirstOperandName { get; set; } public string SecondOperandName { get; set; } public override void Do(ExecutionContext context) { bool result = Compare(Subtract(context)); context.SaveResult(ResultName, result); } protected abstract bool Compare(int compareResult); private int Subtract(ExecutionContext context) { CompareOperand firstOperand = CompareOperand.Create(context.GetResult(FirstOperandName)); CompareOperand secondOperand = CompareOperand.Create(context.GetResult(SecondOperandName)); return (firstOperand - secondOperand); } }
In this class there are:
- two arguments, given their names in the execution context,
- the Do (ExecutionContext) method, which makes a comparison by finding the sign of the difference between two quantities (the Subtract method), and passing this sign to the Abstract method Compare (int). The Compare method we implement in specific inheritors of the CompareCommand class.
- Abstract method Compare (int)
- A specific Subtract method that extracts two operands from the context and stores them in instances of the heirs of the CompareOperand abstract class, and then subtracts one from the other. It is clear that there will also be used polymorphism.
Heirs will look like this:
public class CompareLessThan : CompareCommand { protected override bool Compare(int compareResult) { return compareResult < 0; } }
We now describe the CompareOperand class:
public abstract class CompareOperand { public static CompareOperand Create(object operand) { if (operand is ulong) { return new ULongCompareOperand() { Value = (ulong)operand }; } .. .. .. .. .. .. .. .. .. return new DefaultCompareOperand() { Value = (DefaultType)operand }; } public static int operator -(CompareOperand operand1, CompareOperand operand2) { return operand1 - operand2; } }
As you can see, the static Create method, in accordance with the type of the passed argument, creates an instance of one of the heirs of the CompareOperand class. All its descendants will differ only in the type of the Value property and the implementation of the subtraction operator.
And his heirs will be:
public class LongCompareOperand : CompareOperand { public long Value { get; set; } public static int operator -(LongCompareOperand operand1, LongCompareOperand operand2) { return Math.Sign(operand1.Value - operand2.Value); } }
The scheme looks beautiful except that the subtraction operator is implemented as a static method, and therefore the polymorphism will not work in this case (the static method cannot be virtual, and therefore the polymorphism in its relation will not work). This fact was immediately announced to us by VisualStudio after the launch of the test example - the call of the subtraction operator caused Stack Overflow, since the subtraction implemented in CompareOperand caused itself, and not the polymorphic subtraction operator of the base class. Well, let's do the subtraction not by the “-” operator, but by the Subtract () method.
public abstract class CompareOperand { public static CompareOperand Create(object operand) { return new DefaultCompareOperand() { Value = (DefaultType)operand }; } public abstract int Subtract(CompareOperand operand2); public static int operator -(CompareOperand operand1, CompareOperand operand2) { return operand1.Subtract(operand2); } } public class LongCompareOperand : CompareOperand { public long Value { get; set; } public override int Subtract(CompareOperand operand2) { return Math.Sign(this.Value - ((LongCompareOperand)operand2).Value); } }
The only thing I don’t like in this approach is the chain of if else statements in the CompareOperand.Create () method, but I don’t know how to fix the situation yet. Now, if it becomes necessary to add support for operands for another type, you will need to implement a successor from the CompareOperand class, and add another If else method to the Create () method, which is impossible for a third-party developer who does not have access to the source code. There is also an exception when trying to use two different types of operands within the same comparison.
I did not manage to come up with a universal, flexible solution, and remembering the saying that the best is the enemy of the good, I decided to leave it at that. Thus, we have a set of arithmetic comparison operators, based on the difference between numbers, for all the numeric types supported in the CLR. I think that it can also be supplemented with non-numeric types that support differential operation, such as DateTime.
I hope that the experts present in the blog will offer the most optimal method for solving the task of comparing various types of numerical data.
Link to the fourth part