¿Qué es el abuso de genéricos?

35

Mientras revisaba algún código, noté la oportunidad de cambiarlo para usar genéricos. El código (ofuscado) se ve así:

public void DoAllTheThings(Type typeOfTarget, object[] possibleTargets)
{
    var someProperty = typeOfTarget.GetProperty(possibleTargets[0]);
    ...
}

Este código podría ser reemplazado por genéricos, así:

public void DoAllTheThings<T>(object[] possibleTargets[0])
{
    var someProperty = type(T).getProperty(possibleTargets[0]);
    ...
}

Al investigar los beneficios y las deficiencias de este enfoque, encontré un término llamado abuso genérico . Ver:

Mi pregunta viene en dos partes:

  1. ¿Hay algún beneficio en pasar a genéricos como este? (¿Rendimiento? ¿Legibilidad?)
  2. ¿Qué es el abuso genérico? ¿Y el uso de un genérico cada vez que hay un parámetro de tipo es un abuso ?
SyntaxRules
fuente
2
Estoy confundido. ¿Conoces el nombre de la propiedad de antemano pero no el tipo del objeto, así que usas la reflexión para obtener un valor?
MetaFight
3
@MetaFight Sé que es un ejemplo complicado, el código real sería demasiado largo y difícil de poner aquí. De todos modos, el propósito de mi pregunta es determinar si reemplazar el parámetro Tipo con un genérico se considera buena o mala.
SyntaxRules
12
Siempre he considerado que esa es la razón típica por la que existen los genéricos (juego de palabras delicioso no intencionado). ¿Por qué escribir trucos usted mismo cuando puede aprovechar su sistema de tipos para hacerlo por usted?
MetaFight
2
su ejemplo es extraño, pero tengo la sensación de que es exactamente lo que hacen muchas bibliotecas de IoD
Ewan
14
No es una respuesta, pero vale la pena señalar que el segundo ejemplo es menos flexible que el primero. Hacer que la función sea genérica elimina su capacidad de pasar en un tipo de tiempo de ejecución; Los genéricos necesitan saber el tipo en tiempo de compilación.
KChaloux

Respuestas:

87

Cuando los genéricos se aplican adecuadamente, eliminan el código en lugar de simplemente reorganizarlo. Principalmente, el código que los genéricos son mejores para eliminar son los tipos de letra, la reflexión y la escritura dinámica. Por lo tanto, el abuso de genéricos podría definirse libremente como la creación de código genérico sin una reducción significativa en la tipificación, reflexión o tipeo dinámico en comparación con una implementación no genérica.

Con respecto a su ejemplo, esperaría un uso apropiado de los genéricos para cambiarlo object[]a uno T[]o similar, y evitarlo Typeo por typecompleto. Eso podría requerir una refactorización significativa en otros lugares, pero si el uso de genéricos es apropiado en este caso, debería terminar siendo más simple en general cuando haya terminado.

Karl Bielefeldt
fuente
3
Ese es un gran punto. Admito que el código que me hizo pensar en esto estaba realizando una reflexión difícil de leer. Veré si puedo cambiar object[]a T[].
SyntaxRules
3
Me gusta la caracterización eliminar vs. reorganizar.
cobre
13
Te perdiste un uso clave de genéricos, que es reducir la duplicación. (Aunque puede reducir la duplicación al introducir cualquiera de esos otros pecados que menciona). Si reduce la duplicación sin eliminar los tipos de letra, la reflexión o la escritura dinámica IMO, está bien.
Michael Anderson
44
Excelente respuesta Añadiría que en este caso particular, el OP puede necesitar cambiar a una interfaz en lugar de genéricos. Parece que la reflexión se está utilizando para acceder a propiedades particulares del objeto; una interfaz es mucho más adecuada para permitir que el compilador determine que tales propiedades realmente existen.
jpmc26
44
@MichaelAnderson "eliminar código en lugar de simplemente reorganizarlo" incluye reducir la duplicación
Caleth
5

Usaría la regla sin sentido: los genéricos, como todas las otras construcciones de programación, existen para resolver un problema . Si no hay ningún problema para resolver los genéricos, usarlos es abuso.

En el caso específico de los genéricos, en su mayoría existen para abstraerse de tipos concretos, lo que permite que las implementaciones de código para los diferentes tipos se plieguen en una plantilla genérica (o como lo llame su idioma). Ahora, suponga que tiene un código que usa un tipo Foo. Puede reemplazar ese tipo con un genérico T, pero si solo necesita ese código para trabajar Foo, simplemente no hay otro código con el que pueda plegarlo. Por lo tanto, no existe ningún problema para resolver, por lo que la indirecta de agregar el genérico solo serviría para reducir la legibilidad.

En consecuencia, sugeriría simplemente escribir el código sin usar genéricos, hasta que vea la necesidad de introducirlos (porque necesita una segunda instanciación). Entonces, y solo entonces, es el momento de refactorizar el código para usar genéricos. Cualquier uso antes de ese punto es abuso en mis ojos.


Esto parece sonar demasiado pedante, así que permíteme recordarte:
no hay una regla sin excepciones en la programación. Esta regla incluida.

cmaster
fuente
Pensamientos interesantes Sin embargo, una ligera redacción de sus criterios podría ayudar. Supongamos que OP necesita un nuevo "componente" que asigne un int a una cadena y viceversa. Es muy obvio que resuelve una necesidad común y podría ser fácilmente reutilizable en el futuro si se hace genérico. Este caso estaría en su definición de abuso de genéricos. Sin embargo, una vez que se identificó la necesidad subyacente muy genérica, ¿no valdría la pena invertir un esfuerzo adicional insignificante en el diseño sin que esto sea un abuso?
Christophe
@Christophe Esa es una pregunta difícil. Sí, hay algunas situaciones en las que es mejor ignorar mi regla (¡no hay una regla sin excepción en la programación!). Sin embargo, el caso de conversión de cadena específico en realidad está bastante involucrado: 1. Debe tratar los tipos enteros con y sin signo por separado. 2. Debe tratar las conversiones flotantes por separado de las conversiones enteras. 3. Puede reutilizar una implementación para los tipos enteros más grandes disponibles para tipos más pequeños. Es posible que desee escribir de forma proactiva un contenedor genérico para realizar el casting, pero el núcleo sería sin genéricos.
cmaster
1
Yo argumentaría en contra de la escritura proactiva de un genérico (pre-uso en lugar de reutilización) como probable YAGNI. Cuando lo necesite, refactorice.
Neil_UK
Al escribir esta pregunta, tomé más de la postura de "Escríbelo como genérico si es posible para guardar una posible reutilización futura". Sabía que eso era un poco extremo. Es refrescante ver su punto de vista aquí. ¡Gracias!
SyntaxRules
0

El código se ve extraño. Pero si lo estamos llamando, parece mejor especificar el tipo como genérico.

https://stackoverflow.com/questions/10955579/passing-just-a-type-as-a-parameter-in-c-sharp

Personalmente, no me gustan estas contorsiones que hacen que el código de llamada se vea bonito. por ejemplo, todo lo 'fluido' y los métodos de extensión.

Pero tienes que admitir que tiene un seguimiento popular. incluso Microsoft lo usa en la unidad, por ejemplo

container.RegisterType<IInterface,Concrete>()
Ewan
fuente
0

Para mí, parece que estabas en el camino correcto aquí, pero no terminaste el trabajo.

¿Has considerado cambiar el object[]parámetro Func<T,object>[]para el siguiente paso?

sq33G
fuente