2010-01-12 9 views
5

Necesito una forma de escribir un procedimiento genérico para actuar sobre un tipo de objeto o cualquiera de sus descendientes.cómo evitar de forma segura el error de Delphi: "los tipos de parámetros formales y reales deben ser idénticos"

Mi primer intento fue declarar

procedure TotalDestroy(var obj:TMyObject); 

pero cuando se utiliza con un objeto descendiente

type TMyNewerObject = class(TMyObject); 
var someNewerObject: TMyNewerObject; 

TotalDestroy(someNewerObject); 

me sale el error infame "tipos de parámetros formales y reales deben ser idénticos"

Entonces, mientras luchaba por encontrar una solución, miré el código fuente del procedimiento Delphi system FreeAndNil. Y me encontré con esta declaración impresionante, junto con este sorprendente comentario

{ FreeAndNil frees the given TObject instance and 
    sets the variable reference to nil. 
    Be careful to only pass TObjects to this routine. } 

procedure FreeAndNil(var Obj); 

Evita el tipo de comprobación de errores, pero utiliza ninguna red de seguridad.

Mi pregunta es ... ¿hay alguna manera segura de verificar el tipo de un parámetro var sin tipo?

o en otras palabras, ¿puedes mejorar este código fuente Delphi para que la advertencia no sea necesaria?

procedure FreeAndNil(var Obj); 
var 
    Temp: TObject; 
begin 
    Temp := TObject(Obj); 
    Pointer(Obj) := nil; 
    Temp.Free; 
end; 
+4

¿No cree que el procedimiento hubiera sido escrito de manera diferente si había una manera de hacerlo? –

+0

No lo sé, es por eso que estoy preguntando. –

+3

PA, la respuesta a la pregunta de Lasse es * sí *. Si there * were * una mejor manera, entonces la función de la biblioteca lo habría utilizado. Dado que la función de biblioteca * no * lo usa, podemos concluir con seguridad que no hay mejor manera. –

Respuesta

7

He escrito sobre esto antes, usando un ejemplo muy similar a Lasse's:

A menos que usted está escribiendo una instrucción de asignación a cambio el valor de la entrada el parámetro en sí, y no solo una de sus propiedades, no debe pasar un parámetro por referencia en primer lugar.

Si es escribiendo una instrucción de asignación para cambiar el valor del parámetro, entonces el mensaje del compilador realmente es verdadero, y debe prestarle atención.

Una razón para tener que eludir el error es cuando está escribiendo una función como TApplication.CreateForm. Su trabajo es cambiar el valor del parámetro de entrada, y el tipo del nuevo valor varía y no se puede determinar en tiempo de compilación. Si está escribiendo esa función, entonces su única opción con Delphi es usar un parámetro untyped var, y luego hay una carga adicional tanto para el que llama como para el receptor para asegurarse de que todo vaya bien. La persona que llama necesita asegurarse de que pasa una variable que es capaz de contener valores de cualquier tipo que la función ponga en ella, y la función necesita asegurarse de que almacena un valor de un tipo compatible con lo que solicitó la persona que llamó.

En el caso de CreateForm, la persona que llama pasa un literal de referencia de clase y una variable de ese tipo de clase. La función instancia la clase y almacena la referencia en la variable.

No tengo muy buena opinión de CreateForm o FreeAndNil, en gran parte debido a la forma en que sus parámetros sin tipo sacrifican la seguridad del tipo a cambio de una conveniencia comparativamente pequeña. No ha demostrado la implementación de su función TotalDestroy, pero sospecho que su parámetro var finalmente proporcionará la misma utilidad baja que en esas otras dos funciones. Ver mis artículos sobre ambos:

+0

Como ha adivinado inteligentemente, mi TotalDestroy realiza una limpieza especial de TMyObject y finalmente hace un FreeAndNil. –

+3

Si se requiere una limpieza especial para un 'TMyObject', considere poner eso en el destructor. No obligue a los consumidores de su clase a aprender una nueva forma de destruirlo cuando el sencillo "Gratis" funciona bien para todo lo demás. –

+0

Gracias Rob. Entiendo tu punto, no necesito mi TotalDestroy. Iré solo con mi Destructor destructor que ya tengo. Y deje que el usuario decida si lo quiere a través de FreeAndNil o no. –

9

Vamos a examinar lo que quieres hacer.

Quiere llamar a un método que toma X, pasando un objeto de tipo Y, donde Y es un descendiente de X. El inconveniente, el parámetro es un parámetro "var".

Analicemos qué podría hacer si eso fuera posible.

type 
    TBase = class 
    end; 
    TDescendant = class(TBase) 
    end; 

procedure Fiddle(var x: TBase); 
begin 
    x := TDescendant.Create; 
end; 

type 
    TOtherDescendant = class(TBase) 
    end; 

var a: TOtherDescendant; 
a := TOtherDescendant.Create; 
Fiddle(a); 

Uh-oh, ahora a ya no contiene una instancia de TOtherDescendant, que contiene una instancia de TDescendant. Eso probablemente sea una sorpresa para el código que sigue a la llamada.

Usted no sólo debe tener en cuenta lo que la intención que ver con la sintaxis que se propone, pero efectivamente lo que podría ver con la sintaxis.

Deberías leer la excelente publicación de Eric Lipperts en el blog sobre problemas similares en .NET, que se encuentra aquí: Why do ref and out parameters not allow type variation?.

+0

gracias por su visión y por el enlace. Como comenté a continuación, mi intención es finalmente anular la referencia. –

3

Además de lo que escribió Lasse, que es bastante correcto, la mayoría de las veces no desea pasar un objeto a var parámetro de todos modos.

Un objeto es un tipo de referencia. Lo que ves como el objeto es en realidad una referencia a él. Solo querría pasar un objeto referencia a un parámetro var si quiere cambiar su objeto por un nuevo objeto. Si solo desea poder modificar los miembros del objeto, puede hacerlo simplemente pasándolo a un parámetro normal. Haga que la llamada al método tome un parámetro TMyObject en lugar de un parámetro var TMyObject y debería funcionar.

Por supuesto, si realmente está reemplazando el objeto, entonces no dude en ignorar todo esto, y vea la respuesta de Lasse.

+0

De acuerdo, no pensé en ese ángulo. Muy correcto, no necesitas "var" si lo único que quieres es la referencia. –

+0

de hecho, solo quería anularlo, realizando primero una limpieza especial. –

Cuestiones relacionadas