The identity system in AXSharp allows twin objects to be looked up by a unique numeric identity (ulong). This is used by components that need to resolve references between twin objects at runtime (e.g. the [MemberByIdentity] attribute).
The core class is TwinIdentityProvider, which is responsible for assigning, writing, and sorting identity values.
- During twin construction, each object that implements
ITwinIdentityis registered viaAddIdentity. - When the application starts,
ConstructIdentitiesAsyncis called to assign identity values, write them to the PLC, and build a sorted lookup dictionary. - Other parts of the system can then resolve objects by their identity using
GetTwinByIdentity.
await connector.IdentityProvider.ConstructIdentitiesAsync();ConstructIdentitiesAsync performs the following steps:
- Assign -- Each identity tag is assigned a
ulongvalue using the providedidentityProviderfunction. If no function is supplied, a default provider based onstring.GetHashCode()of the symbol is used. - Write -- The assigned values are written to the PLC via
WriteBatchAsync. Values are always written fresh, regardless of what was previously stored on the PLC. - Sort -- The identities are sorted into a
SortedDictionary<ulong, ITwinIdentity>using the locally assigned values (Cyclic), not values read back from the PLC.
This ensures that stale or inconsistent identity values left on the PLC from prior sessions cannot cause errors.
await connector.IdentityProvider.ConstructIdentitiesAsync(failOnDuplicate: false);By default, ConstructIdentitiesAsync throws a DuplicateIdentityException when two symbols resolve to the same identity value. You can set failOnDuplicate to false to log a warning and skip the duplicate entry instead.
The default identity provider uses string.GetHashCode(), which has two important limitations:
- Not deterministic across processes -- In .NET Core/.NET 5+,
string.GetHashCode()is randomized per process. Identity values will differ between application restarts. - Collision risk --
GetHashCode()returns a 32-bit value. For large PLC programs with many symbols, hash collisions become increasingly likely (birthday paradox).
For production use, supply a custom identity provider that guarantees uniqueness:
await connector.IdentityProvider.ConstructIdentitiesAsync(
identityProvider: tag => tag.Cyclic = MyStableHashFunction(tag.Symbol)
);A good custom provider should:
- Produce deterministic values for the same symbol across process restarts.
- Guarantee uniqueness (or near-uniqueness) across all symbols in the program.
- Use the full 64-bit
ulongrange to minimize collision probability.
This exception is thrown when two different symbols are assigned the same identity value. Common causes:
- Hash collision with the default
GetHashCode()-based provider. Solution: use a custom identity provider with better distribution, or setfailOnDuplicate: falseif identity-based lookups are not critical for your application. - Stale PLC values (resolved in current version) -- Previously, the identity system would read back values from the PLC after writing. If the PLC returned stale data from a prior session, this could cause spurious duplicate errors. The system now uses the locally assigned values directly, avoiding this issue.