Configuring QuickOPC ItemChanged Events After Upgrade

James Nishida7 min read
OPC / OPC UAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The ItemChanged subscription after the upgrade fails because the event expects EasyDAItemChangedEventHandler, not EventHandler<EasyDAItemChangedEventArgs>. Match the event’s delegate and use the non-generic event arguments from OpcLabs.EasyOpc.DataAccess.OperationModel.

Check the ItemChanged delegate before changing the subscription

Use the compiler error as the first branch in the diagnosis. The reported event type is EasyDAItemChangedEventHandler; the handler method must accept the sender and the event-argument type expected by that delegate.

  1. Read the event type. Confirm that IntelliSense or the compiler identifies opc.ItemChanged as an event using EasyDAItemChangedEventHandler. If so, continue with that delegate rather than declaring an EventHandler<T> variable.
  2. Read the event-argument namespace. For the non-generic argument type used by this event, import OpcLabs.EasyOpc.DataAccess.OperationModel. Do not select the generic EasyDAItemChangedEventArgs<T> from OpcLabs.EasyOpc.DataAccess.Generic when the event expects the non-generic type.
  3. Check the handler signature. The method must accept object sender and EasyDAItemChangedEventArgs e. If the method has a different argument type, correct it before attaching the handler.

If these readings match, proceed to the method-group subscription. If the compiler still rejects the handler, inspect the fully qualified event-argument type in the diagnostic: a similarly named type from another namespace is not interchangeable.

Attach the handler using the event’s delegate

The event’s custom delegate and EventHandler<EasyDAItemChangedEventArgs> are distinct delegate types. Matching the argument class alone does not make a variable declared as EventHandler<T> assignable to an event that requires EasyDAItemChangedEventHandler. Attach a compatible method group directly, or explicitly create the event’s delegate type.

using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.OperationModel;

var opc = new EasyDAClient();
opc.ItemChanged += OPCItemChanged;

void OPCItemChanged(object sender, EasyDAItemChangedEventArgs e)
{
    // Process the event after checking e.Exception and e.Vtq.
}

If the application keeps a named delegate variable for later removal, declare it with the event’s delegate type:

EasyDAItemChangedEventHandler itemChangedEventHandler = OPCItemChanged;
opc.ItemChanged += itemChangedEventHandler;

Compile after making this change. A successful build confirms that the method group matches the event contract; it does not confirm that an item subscription is active or that callbacks are arriving. Continue to the runtime check for that.

Separate event attachment from item subscription

Attaching ItemChanged registers the callback with the client. It does not by itself identify which item changes the client should report. Confirm that the application also calls an item-subscription method, such as SubscribeMultipleItems, and that the subscription arguments contain the intended server and item identifiers.

  1. Read the subscription result. Check that the subscription call completes and returns the handles expected by the application. If it fails or returns no usable handles, inspect the subscription arguments and the client’s error information before diagnosing the event handler.
  2. Read callback activity. Log entry into the handler with the event’s item identifier. If callbacks arrive, the delegate and subscription are connected; continue by checking error and value handling. If no callbacks arrive, confirm the subscription call and item arguments first rather than changing the handler signature again.
  3. Check unsubscribe timing. If the application calls UnsubscribeItem, confirm that it uses the intended subscription handle and that the callback test occurs before unsubscription. An unsubscribed item will not continue delivering changes for that subscription.

A sample subscription can include multiple item arguments and a sampling interval, but use the interval and item configuration required by the application. The example’s 1000 value is part of that sample, not a universal setting.

Branch on the callback error and value readings

Once callbacks arrive, inspect e.Exception before reading e.Vtq. An event can represent an error, and e.Vtq may be null in that case. A null value is therefore not sufficient evidence that the item identifier lookup or cache assignment is wrong.

Reading in the callback Interpretation Next check
e.Exception is non-null The event reports an error; value data may be absent. Record or handle the exception and do not dereference e.Vtq.
e.Exception is null and e.Vtq is null There is no value object to read in this callback. Guard the value access and inspect the callback’s error and subscription context.
e.Exception is null and e.Vtq is non-null The value object is available. Read its Value and the item identifier from the event arguments.
void OPCItemChanged(object sender, EasyDAItemChangedEventArgs e)
{
    if (e.Exception != null)
    {
        // Record or handle the reported exception.
        return;
    }

    if (e.Vtq == null)
        return;

    string itemId = e.Arguments.ItemDescriptor.ItemId;
    // Store e.Vtq.Value using the application's cache type and key format.
}

The order of these checks matters: dereferencing e.Vtq.Value before checking for an exception can turn an item-level error into a null-reference failure in application code. After adding the guards, confirm that successful callbacks reach the value-processing path and error callbacks do not.

Read the item identifier from event arguments

After the upgrade, the item descriptor is reached through e.Arguments, not directly through e.ItemDescriptor. Use e.Arguments.ItemDescriptor.ItemId as the cache key when that matches the application’s key scheme. Keep the identifier lookup separate from the value read so the handler can log which item produced each callback.

For example, adapt the cache assignment as follows once the exception and value checks pass:

string itemId = e.Arguments.ItemDescriptor.ItemId;
dataCache[itemId] = e.Vtq.Value;

This assignment assumes the application’s cache accepts a string item identifier and a value compatible with e.Vtq.Value. If its key or value types differ, convert at the cache boundary rather than substituting a different event property. Verify the result by comparing the logged itemId with the item configured in the subscription and checking that the intended cache entry changes.

Check the application’s .NET target separately

A delegate correction does not resolve a framework compatibility problem. The reported application is still on .NET 4.0; the product information in the case identifies QuickOPC 5.23 as the last version built for .NET 3.5 and usable with .NET 3.5 or 4.0, and states that this version is no longer officially supported. Treat framework compatibility as a separate branch from the ItemChanged signature change.

  1. Read the project target. Confirm the application’s actual .NET target and the QuickOPC version referenced by the project.
  2. Compare the pair. If the application must remain on .NET 4.0, compare its referenced version with the stated 5.23 compatibility boundary. Do not assume that a newer library build supports that target.
  3. Choose the supported path. Either keep a version compatible with the existing target while accounting for its unsupported status, or plan a framework upgrade and validate the QuickOPC build against that target. Rebuild and rerun the event checks after changing either side.

Framework support dates and compatibility change independently of the event delegate API. Confirm the target framework and the library’s own compatibility information for the exact versions in the application before selecting a migration path.

Verify the repaired ItemChanged path

Close the diagnosis by verifying the full path in order: the project builds with the correct delegate type, the intended item subscription is active, the handler receives a callback, exception and null-value guards run before value access, and the expected item identifier updates the intended cache entry. If any stage fails, return to that stage’s reading; a later stage cannot prove an earlier one worked.

  1. Build with EasyDAItemChangedEventArgs from OpcLabs.EasyOpc.DataAccess.OperationModel and attach a method matching EasyDAItemChangedEventHandler.
  2. Start the intended subscription and record its outcome before waiting for callbacks.
  3. For each callback, record the item identifier and exception state; read e.Vtq.Value only after the exception and null checks pass.
  4. Confirm that the cache entry for e.Arguments.ItemDescriptor.ItemId receives the expected value, then repeat with an error callback path to confirm it does not dereference a missing value.

FAQ

Why does ItemChanged reject EventHandler<EasyDAItemChangedEventArgs>?

The event expects EasyDAItemChangedEventHandler, a different delegate type. Attach a method with the matching sender and argument signature directly, or declare the variable using the event’s delegate type.

Why does EasyDAItemChangedEventArgs appear in two namespaces?

The generic argument type is in OpcLabs.EasyOpc.DataAccess.Generic; the non-generic event argument type used by this event is in OpcLabs.EasyOpc.DataAccess.OperationModel. Match the type to the event’s declared delegate.

Why is e.Vtq null in an ItemChanged callback?

An error callback can have a null e.Vtq. Check e.Exception first, then check e.Vtq before reading e.Vtq.Value.

Where did ItemDescriptor.ItemId move?

Read the identifier as e.Arguments.ItemDescriptor.ItemId. Use it only after checking the callback’s exception and value state if the handler is also processing e.Vtq.Value.

How do I verify the upgraded ItemChanged handler?

Build with the event’s delegate, confirm the item subscription succeeds, observe a callback, then confirm its identifier and value update the intended cache entry after the exception and null checks pass.

Back to blog