This is indeed how C# works in general, you can’t cast delegates from compatible types directly.
Your last solution of delegateCallback = new EventCallback<ChangeEvent<T>>(callback) is probably the easiest way to go. If you really need to avoid allocations at all costs, then you can always use the RegisterCallback<TEventType, TUserArgsType> method, where you can pass your Action<ChangeEvent> through the userArgs and avoid closing on it.
protected void RegisterChangeEventCallback<T>(VisualElement panel, string elementName, Action<ChangeEvent<T>> callback)
{
var item = panel.Q(elementName);
item.RegisterCallback<ChangeEvent<T>, Action<ChangeEvent<T>>>((e, a) => a(e), callback);
}
This will not allow you to as easily use UnregisterCallback later on, though, so you might want to reconsider, and your last solution was the easiest to read and most flexible in general.
If you really want to have some fun, the following (ugly) alternative would kind of work with UnregisterCallback too, as long as you don’t register multiple callbacks to the same type, in which case you would need more prewrapped helpers, and it starts to become quite messy.
private static class EventCallbackWrapper<TEvent>
{
public static EventCallback<TEvent, Action<TEvent>> WrappedCallback = (e, a) => a(e);
}
protected void RegisterChangeEventCallback<T>(VisualElement panel, string elementName, Action<ChangeEvent<T>> callback)
{
var item = panel.Q(elementName);
item.RegisterCallback<ChangeEvent<T>, Action<ChangeEvent<T>>>(EventCallbackWrapper<ChangeEvent<T>>.WrappedCallback, callback);
}
protected void UnregisterChangeEventCallback<T>(VisualElement panel, string elementName)
{
var item = panel.Q(elementName);
item.UnregisterCallback<ChangeEvent<T>, Action<ChangeEvent<T>>>(EventCallbackWrapper<ChangeEvent<T>>.WrappedCallback);
}