Check if a string contains matching brackets
Let's first take a look at the code 


The code could use some space to breathe by adding spaces before and after operators. For example for(;ptr&lt;arr.Length;ptr++) would be more readable like
for (; ptr &lt; arr.Length; ptr++)  
The expected bracing style would be to place an opening bracket on a new line.  
Omitting braces {} although they might be optional can lead to hidden and therefor hard to find bugs. I would like to encourage you to always use them.  
Comments should describe why something is done. Comments like if ( IsClose(ch) ) // found a closing bracket are superflous and reduce the readability of the code. 
I know that naming things is a hard task but one should at least try to do this in a way that a reader of the code can grasp the meaning of a variable fast which isn'tthe case for oppBracket.  If a method returns a bool one should name that method like a question. E.g AreBracketsProper would be a better name.




Using recursion is a way one can use, but recursion isn't the holy grail for such a task. You need a ref int and an optional oppBracket to achieve the goal by using recursion.  

I would use a Stack&lt;Stack&lt;T&gt;&gt; for keeping track of the nesting levels and instead of two char[] arrays I would use two strings and would use IndexOf(char) to get the index of a bracket and to check if the current char is a bracket at all.  

If I find an opening bracket I will create a new Stack&lt;int&gt; which contains the value of openingBrackets.IndexOf() and then I will Push() it on the Stack&lt;Stack&lt;int&gt;&gt;. If I find an closing bracket I will take the last index from the Stack&lt;int&gt; if ther is any and check if it is the same as the value returned from closingBrackets.IndexOf(). This coul truned into an extension method as well, like so 

static class BracketHelper
{
    private const string openingBrackets = "{[(";
    private const string closingBrackets = "}])";

    public static bool AreBracketsProper(this string value)
    {
        if (value == null) { throw new ArgumentNullException(nameof(value)); }
        if (string.IsNullOrWhiteSpace(value)) { return true; }

        var stacks = new Stack&lt;Stack&lt;int&gt;&gt;();
        var currentStack = new Stack&lt;int&gt;();
        foreach (char c in value)
        {
            var bracketIndex = 0;
            if ((bracketIndex = openingBrackets.IndexOf(c)) &gt; -1)
            {
                currentStack = new Stack&lt;int&gt;();
                currentStack.Push(bracketIndex);
                stacks.Push(currentStack);
            }
            else if ((bracketIndex = closingBrackets.IndexOf(c)) &gt; -1)
            {
                while (currentStack.Count == 0 &amp;&amp; stacks.Count &gt; 0)
                {
                    currentStack = stacks.Pop();
                }

                if (currentStack.Count == 0 || bracketIndex != currentStack.Pop()) { return false; }
            }
        }
        return stacks.Count == 0 || stacks.Pop().Count == 0;
    }
}


Which could be called as an extension method like so   

("{}".AreBracketsProper() == true).Dump();
("{[]()}".AreBracketsProper() == true).Dump();
("{[()][{}]{}}".AreBracketsProper() == true).Dump();
("{[}]}".AreBracketsProper() == false).Dump();
("{[{(}])}".AreBracketsProper() == false).Dump();
("{[][][][{(})}]}".AreBracketsProper() == false).Dump();
("{[]}}".AreBracketsProper() == false).Dump();
("{".AreBracketsProper() == false).Dump();
("}".AreBracketsProper() == false).Dump();