2009-03-18 4 views
14

Independientemente de si esta es una buena idea o no, ¿es posible implementar una interfaz donde la función ejecutora conozca el tipo de objeto que realiza la llamada?Determinar el tipo de objeto llamante en C#

class A 
{ 
    private C; 

    public int doC(int input) 
    { 
     return C.DoSomething(input); 
    } 
} 

class B 
{ 
    private C; 

    public int doC(int input) 
    { 
     return C.DoSomething(input); 
    } 
} 

class C 
{ 
    public int DoSomething(int input) 
    { 
     if(GetType(CallingObject) == A) 
     { 
     return input + 1; 
     } 
     else if(GetType(CallingObject) == B) 
     { 
     return input + 2; 
     } 
     else 
     { 
     return input + 3; 
     } 
    } 
} 

Me parece que esta es una práctica mala codificación (ya que los parámetros no cambian, pero la salida haría) pero aparte de eso es posible?

Estoy en una situación en la que quiero que algunos tipos específicos puedan llamar a cierta función, pero no puedo excluir el acceso a la función. pensé en tener un parámetro "tipo"

DoSomething(int input, Type callingobject) 

Pero no hay garantía de que el objeto que llama usaría GetType (este), en contraposición a GetType (B) para que simule un B con independencia de su propio tipo.

¿Sería esto tan simple (relativamente simple) como examinar la pila de llamadas?


EDITAR

Por favor, vote a la respuesta de JaredPar (si lo desea), así como John Feminella de. No pude marcar ambas respuestas como aceptadas, y acepté la respuesta de John Feminella porque es específicamente lo que pedí, a diferencia de la respuesta de Jared, que planteó una solución que no había considerado anteriormente.

+0

podría darle un poco más de fondo? Porque ahora no veo por qué no implementan métodos separados en C. Algo como C.DoSometingA y C.DoSomethingB. O incluso mejor: pero el comportamiento en las clases A y B. –

+1

La versión corta es que terminaría teniendo 30 o 40 funciones idénticas que simplemente estaban desactivadas en una o dos líneas. – DevinB

+0

@devinb: Ver la edición que hice en mi publicación a continuación. Es posible que desee considerar abrir una pregunta diferente. –

Respuesta

17

Primero, sí, es una idea terrible hacer esto y rompe todo tipo de principios sólidos de diseño. Definitivamente debería considerar un enfoque alternativo si está abierto, como simplemente usar polimorfismo; parece que se puede refactorizar a un caso bastante claro de despacho único.

En segundo lugar, sí, es posible. Usa System.Diagnostics.StackTrace para caminar la pila; luego obtenga el nivel StackFrame apropiado arriba. Luego, determine qué método era el que llama usando GetMethod() en ese StackFrame. Tenga en cuenta que la creación de un seguimiento de pila es una operación potencialmente costosa, y es posible que los llamadores de su método oculten de dónde vienen realmente las cosas.


Editar: Este comentario de la OP hace que sea bastante claro que esto probablemente podría ser un método genérico o polimórfica. @devinb, es posible que desee considerar hacer una nueva pregunta que proporcione más detalles sobre lo que está tratando de hacer, y podemos ver si se presta bien a una buena solución.

La versión corta es que iba a terminar tienen 30 o 40 funciones idénticas que eran simplemente fuera a una o dos líneas. - devinb (hace 12 segundos)

+0

D'oh! Borrando mi "respuesta duplicada". . . tontos, estúpidos y lentos dedos. . . +1 BTW –

+1

Sin sudar - Me hago el ninja todo el tiempo por ese maldito Jon Skeet. Por cierto, no eliminaría tu publicación a menos que sea bastante cercana a un duplicado exacto. Si haces un punto que es en su mayoría similar al mío pero diferente de alguna manera, definitivamente mantenlo así. –

0

Hay (casi) siempre un diseño adecuado que puede lograr lo que necesita. Si das un paso atrás para describir lo que realmente necesitas hacer, estoy seguro de que obtendrás al menos un buen diseño que no requiere que tengas que recurrir a algo como esto.

+0

Mi diseño es una pesadilla muy compacta y autorreferencial, así que sí, he ideado diseños que no tendrían esta particular fealdad, pero tienen fealdad propia. Solo estoy tratando de explorar cada opción. – DevinB

+0

@devinb me parece muy poco probable que las otras opciones sean tan feas como esta –

+0

Son más complicadas. A diferencia de este que es relativamente simple, pero horrible. = D En su mayor parte solo tenía curiosidad. – DevinB

13

Como enfoque alternativo, ¿alguna vez ha considerado ofrecer una clase diferente según el tipo de objeto que solicita la clase?Diga lo siguiente

public interface IC { 
    int DoSomething(); 
} 

public static CFactory { 
    public IC GetC(Type requestingType) { 
    if (requestingType == typeof(BadType1)) { 
     return new CForBadType1(); 
    } else if (requestingType == typeof(OtherType) { 
     return new CForOtherType(); 
    } 
    ... 
    } 
} 

Esto sería un enfoque mucho más limpio que hacer que cada método cambie su comportamiento basado en el objeto que llama. Limpiaría limpiamente las preocupaciones a las diferentes implementaciones de IC. Además, todos podrían proxy a la implementación C real.

EDITAR El examen de la pila de llamadas

Como varias otras personas señalaron que pueda examinar la pila de llamadas para determinar qué objeto está llamando inmediatamente la función. Sin embargo, esta no es una forma infalible de determinar si uno de los objetos que desea un caso especial lo está llamando. Por ejemplo, podría hacer lo siguiente para llamarte desde SomeBadObject, pero es muy difícil para ti determinar que lo hice.

public class SomeBadObject { 
    public void CallCIndirectly(C obj) { 
    var ret = Helper.CallDoSomething(c); 
    } 
} 

public static class Helper { 
    public int CallDoSomething(C obj) { 
    return obj.DoSomething(); 
    } 
} 

Por supuesto, podría caminar más atrás en la pila de llamadas. Pero eso es aún más frágil porque puede ser una ruta completamente legal para que SomeBadObject esté en la pila cuando un objeto diferente llama a DoSomething.

+0

¿Esto todavía requeriría que A y B proporcionen su tipo a CFactory? ¿Cómo evitaría la suplantación de identidad? – DevinB

+0

@devinb, puede caminar la pila para evitar falsificaciones, pero no es una forma infalible. .Net simplemente no fue diseñado para que una función sepa quién llama y, como resultado, cualquier intento de hacer que funcione tendrá agujeros. – JaredPar

+0

Estoy intentando codificar con el paradigma de que un codificador malicioso tendrá acceso a mi fuente, pero no podrá cambiarla. Pero creo que tienes razón en que es casi imposible excluir todas las posibilidades de "badObject" – DevinB

0

Bueno, podrías intentar agarrar el rastro de la pila y determinar el tipo de la persona que llama desde allí, que es una exageración en mi opinión y sería lenta.

¿Qué tal una interfaz, que A,B implementaría?

interface IFoo { 
    int Value { get; } 
} 

Y entonces su método DoSomething se vería así:

public int DoSomething(int input, IFoo foo) 
    { 
     return input + foo.Value; 
    } 
0

Se podría utilizar el System.Diagnostics.StackTrace clase para crear un seguimiento de pila. Luego, puede buscar el StackFrame que está asociado con la persona que llama. StackFrame tiene una propiedad de método que puede usar para obtener el tipo de llamante.

Sin embargo, el método anterior no es nada que deba usarse en el código de rendimiento crítico.

0

Puede examinar la pila de llamadas, pero eso es costoso y frágil. Cuando su código está jit'ed, el compilador puede alinear sus métodos para que, si bien podría funcionar en modo de depuración, podría obtener una pila diferente cuando se compilara en modo de lanzamiento.

2

La respuesta más fácil sería pasar el objeto emisor como cualquier evento con el remitente típico, la metodología eventargs.

Su código de llamada se vería así:

return c.DoSomething(input, this); 

Su método HacerAlgo simplemente comprobar el tipo usando el operador es:

public static int DoSomething(int input, object source) 
{ 
    if(source is A) 
     return input + 1; 
    else if(source is B) 
     return input + 2; 
    else 
     throw new ApplicationException(); 

} 

Esto parece algo con un poco más de programación orientada a objetos. Puede considerar C una clase abstracta con un método, y tener A, B heredando de C y simplemente llamar al método. Esto le permitiría verificar el tipo del objeto base, que obviamente no es falso.

Por curiosidad, ¿qué estás intentando con este constructo?

+0

En líneas generales, C contiene información para A, e información para B, y el método para acceder a esa información es idéntico, pero B no puede acceder (o es ABLE para acceder) a información, y B no puede acceder a información A. ... aproximadamente. – DevinB

+1

Guau, eso me confundió aún más: P ¿Puedes convertirlo en un ejemplo del mundo real? Como un ... ejemplo de gatos y perros o algo así? –

+1

Hmm ... tal vez. Una base de datos contiene una lista de espías estadounidenses y espías rusos. El método devuelve una lista. Cuando los estadounidenses preguntan, obtienen la lista estadounidense, cuando los rusos preguntan, obtienen la lista rusa. – DevinB

0

estructura como un controlador de eventos, estoy seguro de FX COP incluso sugerir lo hace

static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) 
     { 
      throw new NotImplementedException(); 
     } 
+0

... No entiendo. – DevinB

+0

Estoy diciendo usar lo que sugirió Matt Murrell, pero al menos use la misma convención que los eventos. es decir, pasar su objeto de llamada, entonces sus args pares – dfasdljkhfaskldjhfasklhf

2

No es fiable debido a la posibilidad de procesos en línea por el tiempo de ejecución.

+0

Suponga que estas funciones son demasiado grandes para alinearse. – DevinB

0

podría estar en problemas pensando que sería manejar esto de manera diferente, pero ....

Asumo que esto:

clase A pide a la clase E para obtener información
llamadas clase C en la clase E para obtener información
ambos a través del mismo método en E

Usted conoce y creó las tres clases y puede ocultar el contenido de la clase E al público. Si este no fuera el caso, su control siempre se perdería.

Lógicamente: si puede ocultar el contenido de la clase E, puede hacerlo distribuyéndolo a través de una DLL.

Si hace esto de todos modos, ¿por qué no ocultar el contenido de las clases A y C, así (mismo método) pero permitiendo A y C para ser utilizado como base para las clases derivadas B y D.

En las clases A y C tendrías un método para llamar al método en la clase E y entregar el resultado. En ese método (utilizable por la clase derivada, pero el contenido está oculto para los usuarios) puede tener cadenas largas como "claves" pasadas al método en la clase E. Estas cadenas no podrían ser adivinadas por ningún usuario y solo serían conocidas a las clases de base a, C y E.

tener las clases base encapsulan el conocimiento de cómo llamar al método de e correcta sería perfecta aplicación orientada a objetos Creo

Entonces otra vez ... podría estar pasando por alto las complejidades aquí.

4

Comenzando con Visual Studio 2012 (.NET Framework 4.5) puede pasar automáticamente caller information a un método mediante el uso de los atributos del llamante (VB y C#).

public void TheCaller() 
{ 
    SomeMethod(); 
} 

public void SomeMethod([CallerMemberName] string memberName = "") 
{ 
    Console.WriteLine(memberName); // ==> "TheCaller" 
} 

Los atributos del llamante se encuentran en el espacio de nombres System.Runtime.CompilerServices.


Esto es ideal para la implementación de INotifyPropertyChanged:

private void OnPropertyChanged([CallerMemberName]string caller = null) { 
    var handler = PropertyChanged; 
    if (handler != null) { 
     handler(this, new PropertyChangedEventArgs(caller)); 
    } 
} 

Ejemplo:

private string _name; 
public string Name 
{ 
    get { return _name; } 
    set 
    { 
     if (value != _name) { 
      _name = value; 
      OnPropertyChanged(); // Call without the optional parameter! 
     } 
    } 
} 
+0

+1 para señalar esto, porque el propósito de agregar los atributos de información de la persona que llama en 4.5 es un gran ejemplo de contexto en el que esto es realmente necesario, incluso si a primera vista parece violar los buenos principios de diseño. Específicamente, al escribir herramientas de rastreo, depuración o diagnóstico. – BitMask777

+1

@ BitMask777: Sí, otro ejemplo en el que tiene sentido es cuando se implementa 'INotifyPropertyChanged'. –

Cuestiones relacionadas