This is true only in theory, or in extreme cases, like cycles with a huge number of iterations. You can come up with a number of examples where using try would be undesirable because of slowness, but all of these are quite rare cases, most of which mean that either the author of the code writes very bad code that needs optimization and / or refactoring, or wants to use c # for some tasks that are too sensitive in terms of performance, and then probably C # and managed code are not the best choice.
But in 99% of cases, concerns about the “slowness” of try / catch are savings on matches and the voluntary deprivation of the ability to write easily maintained code (all these torments with return codes can bring a lot of headaches).
A little experiment. Do this code here.
static void SomeAction() { var rand = new Random(); for (int i = 0; i < 1000; i++) rand.Next(); } static void Handle() { // просто заглушка } static void WithException(int iterations) { var watch = Stopwatch.StartNew(); try { for (int i = 0; i < iterations; i++) SomeAction(); throw new Exception(); } catch (Exception e) { Handle(); } watch.Stop(); NumberFormatInfo nfi = (NumberFormatInfo) CultureInfo.InvariantCulture.NumberFormat.Clone(); nfi.NumberGroupSeparator = " "; Console.WriteLine("with exception {1} iteration {0} ms", watch.ElapsedMilliseconds, iterations.ToString("n0", nfi)); Console.WriteLine(); } static void WithoutException(int iterations) { var watch = Stopwatch.StartNew(); for (int i = 0; i < iterations; i++) SomeAction(); Handle(); watch.Stop(); NumberFormatInfo nfi = (NumberFormatInfo) CultureInfo.InvariantCulture.NumberFormat.Clone(); nfi.NumberGroupSeparator = " "; Console.WriteLine("without exception {1} iteration {0} ms", watch.ElapsedMilliseconds, iterations.ToString("n0", nfi)); } static void Main(string[] args) { WithoutException(1); // этот и следующий вызов для "прогрева" WithException(1); WithoutException(1); WithException(1); WithoutException(1000); WithException(1000); WithoutException(100000); WithException(100000); WithoutException(500000); WithException(500000); Console.ReadLine(); }
and see the results (you can try to execute it directly on ideone, but it will most likely fall off with a timeout due to the large number of iterations) My results were as follows:

As you can see, the execution time of the code that throws and handles an exception is slightly different from the code without exceptions. But the convenience of error handling is much higher. Of course, the handicraft test is far from perfect, but I assure you that in 99% of cases the problem of throwing and exception handling performance is not worth a damn.
if/switchyou can use the functional paradigm, namely theEithermonad. But if you have not encountered this before, then this approach may seem rather unusual. Here is the implementation of monads (in that chile andEither) in C #. - awesoonif/switch- this is what I focused on. - awesoon