    public sealed class {{ callback.proxy_name }} : {{ callback.public_name }}, global::System.IDisposable
    {
        private BoltFFICallbackHandle _handle;

        internal {{ callback.proxy_name }}(BoltFFICallbackHandle handle)
        {
            _handle = handle;
        }

        ~{{ callback.proxy_name }}()
        {
            Release();
        }

        public void Dispose()
        {
            Release();
            global::System.GC.SuppressFinalize(this);
        }

        internal BoltFFICallbackHandle CloneHandle()
        {
            if (_handle.IsNull) return BoltFFICallbackHandle.Null;
            {{ callback.bridge_name }}.VTable table = Marshal.PtrToStructure<{{ callback.bridge_name }}.VTable>(_handle.vtable);
            var clone = Marshal.GetDelegateForFunctionPointer<{{ callback.bridge_name }}.CloneFn>(table.clone);
            ulong cloned = clone(_handle.handle);
            return cloned == 0 ? BoltFFICallbackHandle.Null : new BoltFFICallbackHandle { handle = cloned, vtable = _handle.vtable };
        }

        private void Release()
        {
            if (_handle.IsNull) return;
            {{ callback.bridge_name }}.VTable table = Marshal.PtrToStructure<{{ callback.bridge_name }}.VTable>(_handle.vtable);
            var free = Marshal.GetDelegateForFunctionPointer<{{ callback.bridge_name }}.FreeFn>(table.free);
            ulong handle = _handle.handle;
            _handle = BoltFFICallbackHandle.Null;
            free(handle);
        }

{% for method in callback.methods %}
{%- match method.proxy %}
{%- when CSharpCallbackProxyPlan::AsyncUnsupported with { public_params, result_type } %}
{%- match result_type %}
{%- when Some with (ty) %}
        public global::System.Threading.Tasks.Task<{{ method.return_type }}> {{ method.name }}({{ public_params }}) => global::System.Threading.Tasks.Task.FromException<{{ ty }}>(new NotSupportedException("async callback proxies are not implemented for C# yet"));
{%- when None %}
        public global::System.Threading.Tasks.Task {{ method.name }}({{ public_params }}) => global::System.Threading.Tasks.Task.FromException(new NotSupportedException("async callback proxies are not implemented for C# yet"));
{%- endmatch %}
{%- when CSharpCallbackProxyPlan::Sync with (proxy) %}
        public {{ proxy.return_type }} {{ method.name }}({{ proxy.public_params }})
        {
            if (_handle.IsNull) throw new ObjectDisposedException(nameof({{ callback.proxy_name }}));
            {{ callback.bridge_name }}.VTable __boltffiTable = Marshal.PtrToStructure<{{ callback.bridge_name }}.VTable>(_handle.vtable);
            var __boltffiInvoke = Marshal.GetDelegateForFunctionPointer<{{ callback.bridge_name }}.{{ method.name }}ProxyFn>(__boltffiTable.{{ method.vtable_field }});
{%- for param in proxy.bridge_params %}
{%- match param %}
{%- when CSharpCallbackBridgeParamPlan::Direct with { public_param, native_param, decoded_arg, proxy_arg } %}
{%- when CSharpCallbackBridgeParamPlan::WireEncoded with { public_param, native_ptr_param, native_len_param, reader_local, decoded_arg, writer, pin_local, ptr_local } %}
            byte[] {{ writer.bytes_binding_name }};
            using (var {{ writer.binding_name }} = new WireWriter({{ writer.size_expr }}))
            {
{%- for stmt in writer.encode_stmts %}
                {{ stmt }};
{%- endfor %}
                {{ writer.bytes_binding_name }} = {{ writer.binding_name }}.ToArray();
            }
            GCHandle {{ pin_local }} = default;
            IntPtr {{ ptr_local }} = IntPtr.Zero;
{%- endmatch %}
{%- endfor %}
{%- if proxy.has_cleanup %}
            try
            {
{%- for param in proxy.bridge_params %}
{%- match param %}
{%- when CSharpCallbackBridgeParamPlan::Direct with { public_param, native_param, decoded_arg, proxy_arg } %}
{%- when CSharpCallbackBridgeParamPlan::WireEncoded with { public_param, native_ptr_param, native_len_param, reader_local, decoded_arg, writer, pin_local, ptr_local } %}
                if ({{ writer.bytes_binding_name }}.Length != 0)
                {
                    {{ pin_local }} = GCHandle.Alloc({{ writer.bytes_binding_name }}, GCHandleType.Pinned);
                    {{ ptr_local }} = {{ pin_local }}.AddrOfPinnedObject();
                }
{%- endmatch %}
{%- endfor %}
{%- match proxy.call %}
{%- when CSharpCallbackProxyCallPlan::Void with { args } %}
                FfiStatus __boltffiStatus;
                __boltffiInvoke({{ args }}, out __boltffiStatus);
                {{ callback.bridge_name }}.ThrowIfCallbackStatus(__boltffiStatus);
{%- when CSharpCallbackProxyCallPlan::Direct with { args, native_out_type, public_expr } %}
                {{ native_out_type }} __boltffiOutPtr;
                FfiStatus __boltffiStatus;
                __boltffiInvoke({{ args }}, out __boltffiOutPtr, out __boltffiStatus);
                {{ callback.bridge_name }}.ThrowIfCallbackStatus(__boltffiStatus);
                return {{ public_expr }};
{%- when CSharpCallbackProxyCallPlan::Encoded with { args, decode_expr, result_decode } %}
                IntPtr __boltffiOutPtr;
                UIntPtr __boltffiOutLen;
                FfiStatus __boltffiStatus;
                __boltffiInvoke({{ args }}, out __boltffiOutPtr, out __boltffiOutLen, out __boltffiStatus);
                try
                {
                    {{ callback.bridge_name }}.ThrowIfCallbackStatus(__boltffiStatus);
                    var __boltffiReader = new WireReader(__boltffiOutPtr, __boltffiOutLen);
{%- if let Some(result_decode) = result_decode %}
                    if (__boltffiReader.ReadU8() != 0)
                    {
                        throw new InvalidOperationException({{ result_decode.err_expr }}.ToString());
                    }
{%- if let Some(ok_expr) = result_decode.ok_expr %}
                    return {{ ok_expr }};
{%- endif %}
{%- endif %}
{%- if let Some(expr) = decode_expr %}
                    return {{ expr }};
{%- endif %}
                }
                finally
                {
                    BoltFFICallbackReturn.Free(__boltffiOutPtr);
                }
{%- endmatch %}
            }
            finally
            {
{%- for param in proxy.bridge_params %}
{%- match param %}
{%- when CSharpCallbackBridgeParamPlan::Direct with { public_param, native_param, decoded_arg, proxy_arg } %}
{%- when CSharpCallbackBridgeParamPlan::WireEncoded with { public_param, native_ptr_param, native_len_param, reader_local, decoded_arg, writer, pin_local, ptr_local } %}
                if ({{ pin_local }}.IsAllocated) {{ pin_local }}.Free();
{%- endmatch %}
{%- endfor %}
            }
{%- else %}
{%- for param in proxy.bridge_params %}
{%- match param %}
{%- when CSharpCallbackBridgeParamPlan::Direct with { public_param, native_param, decoded_arg, proxy_arg } %}
{%- when CSharpCallbackBridgeParamPlan::WireEncoded with { public_param, native_ptr_param, native_len_param, reader_local, decoded_arg, writer, pin_local, ptr_local } %}
            if ({{ writer.bytes_binding_name }}.Length != 0)
            {
                {{ pin_local }} = GCHandle.Alloc({{ writer.bytes_binding_name }}, GCHandleType.Pinned);
                {{ ptr_local }} = {{ pin_local }}.AddrOfPinnedObject();
            }
{%- endmatch %}
{%- endfor %}
{%- match proxy.call %}
{%- when CSharpCallbackProxyCallPlan::Void with { args } %}
            FfiStatus __boltffiStatus;
            __boltffiInvoke({{ args }}, out __boltffiStatus);
            {{ callback.bridge_name }}.ThrowIfCallbackStatus(__boltffiStatus);
{%- when CSharpCallbackProxyCallPlan::Direct with { args, native_out_type, public_expr } %}
            {{ native_out_type }} __boltffiOutPtr;
            FfiStatus __boltffiStatus;
            __boltffiInvoke({{ args }}, out __boltffiOutPtr, out __boltffiStatus);
            {{ callback.bridge_name }}.ThrowIfCallbackStatus(__boltffiStatus);
            return {{ public_expr }};
{%- when CSharpCallbackProxyCallPlan::Encoded with { args, decode_expr, result_decode } %}
            IntPtr __boltffiOutPtr;
            UIntPtr __boltffiOutLen;
            FfiStatus __boltffiStatus;
            __boltffiInvoke({{ args }}, out __boltffiOutPtr, out __boltffiOutLen, out __boltffiStatus);
            try
            {
                {{ callback.bridge_name }}.ThrowIfCallbackStatus(__boltffiStatus);
                var __boltffiReader = new WireReader(__boltffiOutPtr, __boltffiOutLen);
{%- if let Some(result_decode) = result_decode %}
                if (__boltffiReader.ReadU8() != 0)
                {
                    throw new InvalidOperationException({{ result_decode.err_expr }}.ToString());
                }
{%- if let Some(ok_expr) = result_decode.ok_expr %}
                return {{ ok_expr }};
{%- endif %}
{%- endif %}
{%- if let Some(expr) = decode_expr %}
                return {{ expr }};
{%- endif %}
            }
            finally
            {
                BoltFFICallbackReturn.Free(__boltffiOutPtr);
            }
{%- endmatch %}
{%- endif %}
        }
{%- endmatch %}
{% endfor %}
    }
