Iterate, Copy and Transform
The SDK provides various ways how you can loop through the elements of the model, and how these elements can be transformed. Each following section will look into one of the approaches.
OverXOrEmpty
For all the optional lists, there is a corresponding Over{property name}OrEmpty
getter.
It gives you an System.Collection.IEnumerable.
If the property is not set, this getter will give you an empty enumerable.
Otherwise, it will return the enumerable over the list.
For example, see OverSubmodelsOrEmpty
in AasCore.Aas3_0.Environment.OverSubmodelsOrEmpty.
DescendOnce
and Descend
If you are writing a simple script, want to use LINQ and do not care about the performance, the SDK provides two methods in the most general interface IClass, DescendOnce
and Descend
, which you can use to loop through the instances.
Both DescendOnce
and Descend
iterate over model children of an IClass.
DescendOnce
, as it names suggests, stops after all the children has been iterated over.
Descend
continues recursively to grand-children etc.
Here is a short example how you can get all the properties from an environment whose ID-short starts with another
:
using System.Collections.Generic;
using System.Linq;
using Aas = AasCore.Aas3_0;
public class Program
{
public static void Main()
{
// Prepare the environment
var someProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "someProperty",
};
var anotherProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "anotherProperty"
};
var yetAnotherProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "yetAnotherProperty"
};
var submodel = new Aas.Submodel(
"some-unique-global-identifier")
{
SubmodelElements = new List<Aas.ISubmodelElement>()
{
someProperty,
anotherProperty,
yetAnotherProperty
}
};
var environment = new Aas.Environment()
{
Submodels = new List<Aas.ISubmodel>()
{
submodel
}
};
// Iterate over all properties which have "another"
// in the ID-short
foreach (
var prop in environment
.Descend()
.OfType<Aas.IProperty>()
.Where(
prop => (
prop.IdShort != null
&& prop.IdShort.ToLower().Contains("another")
)
)
)
{
System.Console.WriteLine(prop.IdShort);
}
// Outputs:
// anotherProperty
// yetAnotherProperty
}
}
Iteration with Descend
and DescendOnce
works well if the performance is irrelevant.
However, if the performance matters, this is not a good approach.
First, all the children model elements will be visited (even though you need only a small subset).
Second, the call to LINQ's OfType<Aas.IProperty>
needs to perform a type cast for every child.
Let's see in the next section how we could use a more efficient, but also a more complex approach.
Visitor
Visitor pattern is a common design pattern in software engineering. We will not explain the details of the pattern here as you can read about in the ample literature in books or in Internet.
The cornerstone of the visitor pattern in double dispatch: instead of casting to the desired type during the iteration, we add a method Accept
to IClass, whose implementations then directly dispatch to the appropriate method.
This allows us to spare casts and directly dispatch the execution.
The SDK already implements Accept
methods, so you only have to implement the visitor.
The visitor class has a visiting method for each class of the meta-model. In the SDK, we provide different flavors of the visitor abstract classes which you can readily implement:
- AbstractVisitor which needs all the visit methods to be implemented,
- VisitorThrough which visits all the elements and does nothing, and
- AbstractVisitorWithContext which propagates a context object along the iteration.
Let us re-write the above example related to Descend
method with a visitor pattern:
using System.Collections.Generic;
using Aas = AasCore.Aas3_0;
using AasVisitation = AasCore.Aas3_0.Visitation;
class Visitor : AasVisitation.VisitorThrough
{
public override void Visit(Aas.Property prop)
{
if (prop.IdShort.ToLower().Contains("another"))
{
System.Console.WriteLine(prop.IdShort);
}
}
};
public class Program
{
public static void Main()
{
// Prepare the environment
var someProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "someProperty",
};
var anotherProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "anotherProperty"
};
var yetAnotherProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "yetAnotherProperty"
};
var submodel = new Aas.Submodel(
"some-unique-global-identifier")
{
SubmodelElements = new List<Aas.ISubmodelElement>()
{
someProperty,
anotherProperty,
yetAnotherProperty
}
};
var environment = new Aas.Environment()
{
Submodels = new List<Aas.ISubmodel>()
{
submodel
}
};
// Iterate over all properties which have "another"
// in the ID-short
var visitor = new Visitor();
visitor.Visit(environment);
// Outputs:
// anotherProperty
// yetAnotherProperty
}
}
There are important differences to iteration with Descend
:
- Due to double dispatch, we spare a cast. This is usually more efficient.
- We can handle multiple types of the elements, not only a single type (
Property
in this case). This can allow for better readability of the code as well as better performance if two or more element types need to be considered in one iteration. - The iteration logic in
Descend
lives very close to where it is executed. In contrast, the visitor needs to be defined as a separate class. While sometimes faster, writing the visitor makes the code less readable.
Descend or Visitor?
In general, people familiar with the visitor pattern and object-oriented programming will prefer, obviously, visitor class.
People who like LINQ will prefer Descend
.
It is difficult to discuss different tastes, so you should probably come up with explicit code guidelines in your code and stick to them.
Make sure you always profile before you sacrifice readability and blindly apply one or the other approach for performance reasons.
Shallow and Deep Copies
In the static class [Copying], we provide methods for making shallow and deep copies of an instance of AAS model.
In both manners of copying, primitive values (such as bool
, string
etc.) are copied by value.
Shallow copying copies all the non-primitive values by reference. The lists are also copied by reference, and no new lists are created in the copy.
Deep copying makes a deep copy recursively, where we make a deep copy of all the underlying non-primitive values.
Here is an example of how you can make a shallow and a deep copy of an [Environment]:
using System.Collections.Generic;
using System.Linq;
using Aas = AasCore.Aas3_0;
public class Program
{
public static void Main()
{
// Prepare the environment
var someProperty = new Aas.Property(
Aas.DataTypeDefXsd.Boolean)
{
IdShort = "someProperty",
};
var submodel = new Aas.Submodel(
"some-unique-global-identifier")
{
SubmodelElements = new List<Aas.ISubmodelElement>()
{
someProperty
}
};
var environment = new Aas.Environment()
{
Submodels = new List<Aas.ISubmodel>()
{
submodel
}
};
// Make a deep copy
var deepCopy = Aas.Copying.Deep(environment);
// Make a shallow copy
var shallowCopy = Aas.Copying.Shallow(environment);
// Changes to the property affect only the shallow copy,
// but not the deep one
environment.Submodels[0].SubmodelElements![0].IdShort = "changed";
System.Console.WriteLine(
shallowCopy.Submodels![0].SubmodelElements![0].IdShort);
System.Console.WriteLine(
deepCopy.Submodels![0].SubmodelElements![0].IdShort);
// Output:
// changed
// someProperty
}
}
Transformer
A transformer pattern is an analogous to visitor pattern, where we "transform" the visited element into some other form (be it a string or a different object). It is very common in compiler design, where the abstract syntax tree is transformed into a different representation.
The SDK provides two different flavors of a transformer:
- AbstractTransformer, where the model element is directly transformed into something, and
- AbstractTransformerWithContext, which propagates the context object along the transformations.
Since we need to provide a transformation method for each class of the meta-model, we deliberately omit an example due to the length of the code. If you need a practical example, see the source code of the Verification static class, where we implemented the verification logic using an AbstractTransformer.