2011-11-08 14 views
11

Tengo problemas para obtener el compilador de C# para llamar a un método de extensión que he creado, ya que prefiere un método de instancia con un argumento params en su lugar.¿Cómo forzar el uso del método de extensión en lugar del método de instancia con params?

Por ejemplo, decir que tengo la siguiente clase y su método:

public class C 
{ 
    public void Trace(string format, params object[] args) 
    { 
     Console.WriteLine("Called instance method."); 
    } 
} 

Y y extensión:

public static class CExtensions 
{ 
    public void Trace(this C @this, string category, string message, params Tuple<string, decimal>[] indicators) 
    { 
     Console.WriteLine("Called extension method."); 
    } 
} 

En mi programa de ejemplo:

pubic void Main() 
{ 
    var c = new C(); 

    c.Trace("Message"); 
    c.Trace("Message: {0}", "foo"); 
    c.Trace("Category", "Message", new KeyValuePair<string, decimal>("key", 123)); 
} 

Todas las llamadas de impresión Called instance method..

No tengo acceso a la clase C, obviamente, o no me molestaría en crear métodos de extensión, y mi extensión es importante porque permitiría a mis usuarios continuar utilizando una clase que ya conocen con algún valor agregado.

Por lo que he entendido, el compilador favorecerá los métodos de instancia sobre los métodos de extensión, pero ¿es esta la única regla? Eso significaría que cualquier clase con un método que se parece a Method(string format, params object[] args) no puede tener métodos de extensión con un primer parámetro del tipo string.

Cualquier explicación sobre las razones de este comportamiento o una forma de evitarlo (es decir, no "simplemente llame al CExtensions.Trace(c, "Category", ...") sería muy apreciado.

+0

Me he encontrado con esto antes. Parece que la única solución es cambiar el nombre del método. Me interesaría ver si hay una solución. – Polynomial

Respuesta

5

No puede usar extensiones para "hacerse cargo" de los métodos de clase existentes.

Si la llamada funciona sin la extensión, el comportamiento no debería cambiar cuando agrega la extensión. La razón para esto es que el código existente no debería romperse al introducir una extensión más adelante.

Tienes que usar un nombre diferente para él, o usar tipos de parámetros diferentes del método de la clase.

+0

Aunque muchas personas dieron buenas sugerencias de soluciones (aunque ninguna me funcionó), considero que esta es la mejor respuesta ya que resume perfectamente de dónde viene mi problema. – madd0

4

No se puede directamente. Siempre se prefiere un método en la instancia de destino sobre un método de extensión. El único forma de hacer esto (manteniendo los nombres iguales, etc.) es usar el enfoque CExtensions.Trace (en la pregunta).

En algunos casos, un truco aquí sería utilizar alguna clase de base de C o interfaz que C implementos, pero , que no tiene un método Trace y vuelva a escribir la variable, y añadir una sobrecarga en CExtensions, es decir

IFoo foo = c; 
foo.Trace(...); 
+0

Me gusta el truco de agregar el método de extensión a un tipo base. Por supuesto, eso hubiera sido demasiado fácil y, en mi caso, tendría que extender 'object' ... – madd0

0

Creo que se puede cambiar primer tipo de parámetros, o el nombre de la función (TraceFormat?)

Esta versión params se ve muy codicioso, así que si usted preserva como primer argumento str Esto siempre captará todas las llamadas e ignorará el método de extensión, creo.

+0

Realmente no quiero cambiar el nombre del método, porque es algo que los desarrolladores están acostumbrados a . Cambiar el nombre significaría que el método no se usaría como quisiéramos. Cambiar el tipo del primer argumento a algo que no sea 'cadena' sería difícil, ya que realmente quiero un nombre de categoría como 'cadena'. – madd0

+0

en realidad el primer argumento, ya que la Categoría parece ser un buen candidato para convertirse en Enum, por ejemplo, y comenzará a funcionar. –

+0

Valentin, buen intento, pero para algunos de los equipos, la "categoría" podría ser, por ejemplo, el nombre de un cliente. Como realmente espero que nuestra base de clientes siga creciendo, prefiero evitar un 'enum' que debería actualizarse constantemente;) – madd0

2

Tuple<string, decimal> no es lo mismo que KeyValuePair<string, decimal>. Por lo tanto, KeyValuePair<string, decimal> pasado se toma como un objeto, por lo tanto, se utiliza el método de miembro con params object[] args.

De hecho KeyValuePair es un estructura.

+0

'Tuple' o' KeyValuePair', en realidad no cambia mucho, ambos tipos -cualquier tipo para el caso- serán "absorbidos" por la matriz 'object'. – madd0

+0

Eso es cierto, pero si usa un 'Tuple ' en la llamada, aún no usará el método de extensión. – Guffa

+0

Sí, acabo de probar y no lo estoy usando. – Aliostad

2

Puede hacerlo con argumentos con nombre:

c.Trace(
    "Category", 
    "Message", 
    indicators: new Tuple<string, decimal>("key", 123)); 

pero perder la funcionalidad params y que tendría que pasar explícitamente una matriz para el argumento de indicadores, como la siguiente:

c.Trace(
    "Category", 
    "Message", 
    indicators: new Tuple<string, decimal>[] 
    { 
     new Tuple<string, decimal>("key", 123), 
     new Tuple<string, decimal>("key2", 123) 
    }); 
+0

funcionará, pero es fácil olvidar este argumento con nombre y llamaremos silenciosamente a la versión predeterminada sin que se note esto. –

+0

@Valentin Kuzub, pero otras opciones también son propensas a errores. Es posible que olvide que necesita llamar al método con un nombre diferente o una firma diferente. –

+0

El argumento de João es exactamente por qué no quisiera cambiar el nombre o la firma del método, pero Valentin tiene razón: es muy probable que uno olvide usar el argumento nombrado y llame a la versión predeterminada sin siquiera darse cuenta (o solo cuando sea demasiado tarde) – madd0

Cuestiones relacionadas