Variables locales y valores de retorno por referencia (y 2)

En la entrada anterior presentamos otra de las novedades introducidas en C# 7.0: las referencias locales, o variables locales que contienen referencias (el término más común con el que se intenta comúnmente disfrazar a los punteros). Hoy toca hablar de cómo a partir de C# 7.0 es posible también definir métodos que devuelvan referencias; esta posibilidad normalmente sirve como contrapartida a la anterior (y por eso casi siempre se describen juntas), porque frecuentemente encontrará que el valor que se asigna a una referencia local es precisamente el resultado devuelto por una función que hace uso del retorno de una referencia.

En aquella ocasión mencionamos cómo el objetivo central de esta introducción camuflada de punteros es el de ayudar a incrementar el rendimiento en escenarios en los que no sea posible o conveniente recurrir al código no seguro (unsafe code). Pues bien, tal vez la situación más típica en la que es bueno utilizar punteros desde el punto de vista de una mayor velocidad de ejecución es cuando se necesita manipular los elementos de un array. Por esta razón, para el ejemplo de hoy utilizaremos una versión del reciente programa que cuenta las apariciones de las diferentes palabras reservadas de C#, en la que sustituiremos el diccionario que allí se utilizaba un por sencillo array:

    private static KeywordInfo[] dict;

    private static void InitializeDict()
    {
        dict = new KeywordInfo[keywords.Length + contextualKeywords.Length];

        int i = 0;
        foreach (var s in keywords) 
            dict[i++] = new KeywordInfo(s, false);
        foreach (var s in contextualKeywords) 
            dict[i++] = new KeywordInfo(s, true);
        Array.Sort(dict);
    }

Cada vez que el algoritmo que recorre los ficheros de código fuente encuentra una palabra reservada, debe buscarla en este array de estructuras e incrementar el contador correspondiente. La versión anterior del programa se apoyaba en un método llamado GetKeywordIndex que devolvía el índice de la palabra reservada en el array; en su lugar, aquí utilizaremos el método GetKeywordReference, que devuelve una referencia al elemento encontrado:

    private static ref KeywordInfo GetKeywordReference(string keyword)
    {
        for (int i = 0; i < dict.Length; i++)
        {
            int result = string.Compare(dict[i].Keyword, keyword);
            if (result == 0)
                return ref dict[i];
            else if (result > 0)
                break;
        }
        throw new InvalidOperationException($"Keyword missing: {keyword}");
    }

Dado el método anterior, el código que hace la llamada tendrá la siguiente apariencia:

        var text = childToken.Text;
        ref KeywordInfo keywordInfo = ref GetKeywordReference(text);
        keywordInfo.Count++;

Observe nuevamente la presencia del prefijo ref a ambos lados de la asignación.

Es de destacar también que el compilador de C# 7.0 lleva a cabo un sofisticado análisis de flujo para impedir que un método pueda devolver una referencia a algo que no estará disponible en el contexto en el que éste pueda ser llamado; por ejemplo, una referencia a una variable local del propio método, que dejará de existir tan pronto el método retorne.

La utilización del retorno de referencias en situaciones adecuadas (y éste se me antoja un buen ejemplo) podría resultar en un incremento perceptible del rendimiento.