Difference between ObjectPool.Clear() and Dispose()?

They have the same description in API. Are they really the same? If so why do we have 2 same functions?

Dispose() is generally part of the IDisposable interface.

It may ultimately do the same thing but it is still an interface.

Before you destroy your project by adding object pooling, consider this:

The costs and issues associated with object pooling / pools:

In very rare extremely-high-count object circumstances I have seen small benefits from pooling.

In 100% of ALL circumstances, object pooling is a source of constant bugs and edge case disasters.

I’m just answering from my phone, so this is not the most detailed answer. Without investigating anything (I don’t use anyone’s object pools but my own) I would guess it’s because whatever ObjectPool class you’re referring to (there are many that exist in the world and you didn’t provide a namespace) implements the IDisposable interface. There is much you can research on your own about IDisposable. I don’t really like the pattern, but it is necessary to dispose of native, unmanaged resources like a file handle or something. While not strictly required, it is beneficial for performance to call Dispose(), so then you must have everything that contains a legitimate IDisposable reference to also become IDisposable as well. So it’s one of those virally contagious patterns that infects your whole code base that Microsoft loves, like async/await. That’s because the original, valid disposable should be coded properly to dispose of the native resource automatically with it’s finalizer during garbage collection even if nobody properly disposed it, but disposing of it manually tells the garbage collector to suppress the finalizer, which makes the garbage collection process more efficient.

Sometimes people use (abuse?) IDisposable to put something in a using block, so that something is guaranteed to happen when you’re done with it. It’s the same as a try / finally, just syntactic sugar. For an object pool that usually means freeing managed resources, like just returning memory to some other memory pool. I don’t like that trend because it’s wonky, and usually I’m not newing up something in a using statement if I’m going for pooling and automatic memory management and efficiency. I would rather people read the docs and just know when to call Clear() or equivalent on their own and they just do it or fail to, that’s on them. That’s for managed C# resources mind you, things that just get garbage collected anyway in the worst case scenario, but someone made it IDisposable just for the using statement trick. Put it in your own try finally if you need to. Microsoft tries to get too high on syntactic sugar.

They were probably just copying patterns seen in .NET source code that does the same thing where a common method like Clear() is all that really happens in Dispose() and it’s only IDisposable so you can use the using block trick, but that makes no sense anyway. Why would you new up a whole object pool in a using statement block??

You are right, Dispose is just the interface implementation for Clear, literally. Well, a using block “may” make sense in a coroutine :slight_smile: Though it would still be a quite strange usage. Though who knows what people come up with. Having the interface doesn’t really hurt. Missing it is always a bigger problem.

Haha. Syntactic sugar on syntactic sugar. We’re making a layer cake now! You may be right, but I don’t even want to see what the actual compiled code looks like. :smile:

This is my favorite article on IDisposable for anyone interested: https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

I feel like there is a lot of .NET code that gets created for what they see as typical enterprise applications. Those types of apps are more event driven. The user presses a button, or requests a particular URL, and then some method in your app gets invoked. So, they write code like using(new FileStream(… yada, yada. But, I don’t like that kind of code in Unity. For one, the performance considerations are totally different. What they consider a fast operation in an enterprise application may be considered abysmally slow during a single frame of a Unity app. Also, I’m usually holding onto things like object pools, or even a FileStream for more than a single method call, and if I need to gracefully dispose of that object I have a place in the app where that occurs, but it’s not within the same method and a try/finally block can’t really wrap around the whole asynchronous lifetime of that object across many frames in Unity.

Then, on top of that you get a bit of cargo cult programming where .NET has set the pattern and then people just keep copying it because they’ve seen it before, and encourage people to do weird things with using statements. The justification is often “readability,” but to me readability also includes debugability. It’s not more debuggable when you aren’t even looking at what the actual C# code is going to be at the end of the day. /rant

This is a bit of a tangent now, but your comment about a using statement within a coroutine got me thinking about a great interview question if I were one of those types of people who asked cursed interview questions. (I subsititued an async/await method for an IEnumerator/yield iterator method, but it’s the same concept.)

“What does this method actually do?”

using System.IO;
using System.Text;
using System.Threading.Tasks;

public class HelloWorld
{
    private const string FilePath = @"C:\HelloWorld.txt";
    private const string Text = "Hello world!";
    private readonly byte[] Bytes = Encoding.UTF8.GetBytes(Text);

    public async Task HelloWorldAsync()
    {
        using FileStream fileStream = new(FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true);
        for(int i = 0; i < Bytes.Length; i++)
        {
            await fileStream.WriteAsync(Bytes[i..i]);
        }
    }
}

:]

This is how SharpLab.io translates it:
Translation

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]

[NullableContext(1)]
[Nullable(0)]
public class HelloWorld
{
    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <HelloWorldAsync>d__3 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder <>t__builder;

        [Nullable(0)]
        public HelloWorld <>4__this;

        [Nullable(0)]
        private FileStream <fileStream>5__2;

        private int <i>5__3;

        private ValueTaskAwaiter <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            HelloWorld helloWorld = <>4__this;
            try
            {
                if (num != 0)
                {
                    <fileStream>5__2 = new FileStream("C:\\HelloWorld.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 4096, true);
                }
                try
                {
                    if (num != 0)
                    {
                        <i>5__3 = 0;
                        goto IL_00e7;
                    }
                    ValueTaskAwaiter awaiter = <>u__1;
                    <>u__1 = default(ValueTaskAwaiter);
                    num = (<>1__state = -1);
                    goto IL_00ce;
                    IL_00ce:
                    awaiter.GetResult();
                    <i>5__3++;
                    goto IL_00e7;
                    IL_00e7:
                    if (<i>5__3 < helloWorld.Bytes.Length)
                    {
                        awaiter = <fileStream>5__2.WriteAsync(RuntimeHelpers.GetSubArray(helloWorld.Bytes, new Range(<i>5__3, <i>5__3))).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            num = (<>1__state = 0);
                            <>u__1 = awaiter;
                            <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                            return;
                        }
                        goto IL_00ce;
                    }
                }
                finally
                {
                    if (num < 0 && <fileStream>5__2 != null)
                    {
                        ((IDisposable)<fileStream>5__2).Dispose();
                    }
                }
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <fileStream>5__2 = null;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <fileStream>5__2 = null;
            <>t__builder.SetResult();
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
            <>t__builder.SetStateMachine(stateMachine);
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    private const string FilePath = "C:\\HelloWorld.txt";

    private const string Text = "Hello world!";

    private readonly byte[] Bytes = Encoding.UTF8.GetBytes("Hello world!");

    [AsyncStateMachine(typeof(<HelloWorldAsync>d__3))]
    public Task HelloWorldAsync()
    {
        <HelloWorldAsync>d__3 stateMachine = default(<HelloWorldAsync>d__3);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
        stateMachine.<>4__this = this;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }
}

What’s funny is the code in question is essentially taken straight from example code in the official docs.

Thank you, I hate it.