Is it possible to serialize a TCollection which is not encapsulated in a TComponent ?
For example, I have a custom TCollection. I can't use TMemoryStream.WriteComponent() on my TCollection descendant. It'll only works if I encapsulate the collection in a TComponent and then if I write this component.
Technically there is no problem but declaring a TComponent which only owns a TCollection seems a bit odd.
TMyCustomCollection = Class(TCollection) // not serializable ?
//...
End;
TMyCustomCollectionCapsule = Class(TComponent) // serializable !
Private
FMyCusColl: TMyCustomCollection;
Procedure SetMyCusColl(Const Data: TMyCustomCollection);
Published
Property CanBeSerialized: TMyCustomCollection Read FMyCusColl Write SetMyCusColl
End;
Maybe I just miss a feature of the Delphi RTL? Can a TPersistent descendent be streamed without being itself encapsulated in a TComponent ?
It only works if you put your collection in a TComponent, because TMemoryStream.WriteComponent (the name itself is a clue!) takes a TComponent as a parameter:
procedure WriteComponent(Instance: TComponent);
and TCollection is as you already discovered not a TComponent descendant. It may seem odd to have a TComponent descendant just to hold your TCollection descendant, but if you want to stream it using the WriteComponent facilities of streams, I don't see any other easy way to do it.
If you want to do this using "just" the RTL/VCL (ie not using a third party library), you would have to write a T(Memory)Stream descendant and add a WritePersistent implementation that takes an Instance: TPersistent parameter.
I haven't delpheddelved into the TStream classes that much, but my guess is that you w/should be able to borrow a lot from the TComponent support. Certainly the class inheritance support.
Having had a cursory look, it seems simple at first as WriteComponent just calls WriteDescendent which instantiates a TWriter and then calls the WriteDescendent method of that writer. And the TWriter already contains methods to write a collection.
However, if you "just" want to stream TPersistent descendants, you will have to do a lot of work in TWriter/TReader as well as they are completely based around TComponent. And it won't be a simple case of just writing a couple of descendant. For one, they TWriter/TReader are not really set up to be derived from. For another: TStream (descendants) instantiate TWriter and TReader directly and these classes do not have virtual constructors. Which makes writing descendants for them fairly futile unless you would like to try your hand at hooking, patching the VMT and more of that interesting stuff.
All in all: the easiest way to stream your custom collection remains to "just" wrap it in a TComponent and live with the "waste" of that.
You can serialize a TCollection not encapsuled within a TComponent by means of another TComponent descendant defined as follows:
type
TCollectionSerializer = class(TComponent)
protected
FCollectionData: string;
procedure DefineProperties(Filer: TFiler); override;
public
procedure WriteData(Stream: TStream);
procedure ReadData(Stream: TStream);
//
procedure LoadFromCollection(ACollection: TCollection);
procedure SaveToCollection(ACollection: TCollection);
end;
DefineProperties, WriteData and ReadData implementation details:
procedure TCollectionSerializer.WriteData(Stream: TStream);
var
StrStream: TStringStream;
begin
StrStream:=TStringStream.Create;
try
StrStream.WriteString(FCollectionData);
Stream.CopyFrom(StrStream,0);
finally
StrStream.Free;
end;
end;
procedure TCollectionSerializer.ReadData(Stream: TStream);
var
StrStream: TStringStream;
begin
StrStream:=TStringStream.Create;
try
StrStream.CopyFrom(Stream,0);
FCollectionData:=StrStream.DataString;
finally
StrStream.Free;
end;
end;
procedure TCollectionSerializer.DefineProperties(Filer: TFiler);
begin
inherited;
//
Filer.DefineBinaryProperty('CollectionData', ReadData, WriteData,True);
end;
Templates of LoadFromCollection and SaveToCollection:
procedure TCollectionSerializer.LoadFromCollection(ACollection: TCollection);
var
CollectionStream: TStream;
begin
CollectionStream:= TCollectionStream.Create(ACollection);
try
ReadData(CollectionStream)
finally
CollectionStream.Free;
end;
end;
procedure TCollectionSerializer.SaveToCollection(ACollection: TCollection);
var
CollectionStream: TStream;
begin
CollectionStream:= TCollectionStream.Create(ACollection);
try
WriteData(CollectionStream);
finally
CollectionStream.Free;
end;
end;
About TCollectionStream:
It should be a TStream descendant having a rich creator with a TCollection as a parameter and designed to behave like a TFileStream. You must implement it. Disclaimer: I have never tested that but I can tell that a TFileStream works (for streaming external file).
Conclusion:
This component is inspired by the VCL way to serialize within a DFM an external file under Delphi XE (RCData). It must be registred along with a component editor (that you must also implement based on TComponentEditor) doing the serialization at designtime.
Related
This question already has an answer here:
Destructor without an override directive
(1 answer)
Closed 6 months ago.
I have multiple descendent classes sharing an ancestor. When I want to destroy an object I don't know what the actual type is, because the pointer holding the actual object has an ancestor type.
Thinks of it like you have multiple descendent from TButton, saving all objects in an array of type TButton, now when you want to destroy the objects, calling TButton(ButtonArray[I]).Destroy cause memory leaks, because it didn't call Destroy method of descendent types.
I want an approach to properly destroy descendent classes from the ancestor Destroy method.
This problem doesn't exist.
Since the destructor Destroy is a virtual method, the correct one will be chosen automatically at run-time, because Delphi knows the actual class of every object instance, no matter what kind of pointers you have to it in your source code. (Indeed, try ShowMessage(MyObj.ClassName).)
For example, given TFruit, TApple = class(TFruit), and TBanana = class(TFruit) and
var
a, b: TFruit;
begin
a := TApple.Create;
b := TBanana.Create;
a.Destroy; // calls TApple.Destroy, if properly overridden
b.Destroy; // calls TBanana.Destroy, if properly overridden
end;
(except that, of course, you never write x.Destroy but x.Free).
So why doesn't this work for you? Well, this is a common mistake:
TChild = class(TParent)
constructor Create;
destructor Destroy;
end;
must be
TChild = class(TParent)
constructor Create;
destructor Destroy; override; // <-- Add override!
end;
Otherwise you will not override TParent.Destroy, but simply introduce a new destructor with the same name.
Actually, the compiler tries to warn you about this:
[dcc32 Warning] W1010 Method 'Destroy' hides virtual method of base type 'TObject'
or something like that.
With the aim of reducing testing and repeating code I have discovered the multiple inheritance via generic mix-in but I don't know how is the best way to achieve that, as well as best practices.
Having the following class hierarchy example (done with diagrams.net):
This is the spec of the generic:
generic
type S is abstract tagged private;
package GenericChild is
type GenericChild_T is abstract new S with private;
procedure P_SetGenericAttribute (Self : in out GenericChild_T;
i : Integer);
function F_GetGenericAttribute (Self : GenericChild_T)
return Integer;
private
type GenericChild_T is abstract new S with
record
GenericAttribute : Integer;
end record;
end GenericChild;
For the generic instantiation in Child1:
with Root;
with GenericChild;
package Child1 is
type Child1_T is new Root.Root_T with private;
private
package Child1_G is new GenericChild (Root.Root_T);
type Child1_T is new Child1_G.GenericChild_T with null record;
end package Child1;
I can use the methods defined on the Root_T class without problems but, when I try to use the generic methods that is what I get:
no selector "P_SetGenericAttribute" for private type "Child1_T" ...
This is the main.adb where I have tested that:
with Child1;
procedure Main is
Instance : Child1.Child1_T;
begin
Instance.P_SetRootAttribute(1); --ok
Instance.P_SetGenericAttribute(1); --illegal
end main;
Why? Because I have encapsulated the generic package instantiation?
In that case, how is the best way to solve it? Creating public methods in the child classes and calling the private generic instantiation methods within the method implementation? Because I would like to keep the generic instantiation as private. By doing this I'm able to set and get the attribute of the generic.
These are the changes performed to child1.ads that works for me:
with Root;
with GenericChild;
package Child1 is
type Child1_T is new Root.Root_T with private;
procedure P_SetGenericAttribute (Self : in out Child1_T;
i : Integer);
function F_GetGenericAttribute (Self : Child1_T)
return Integer;
private
package Child1_G is new GenericChild (Root.Root_T);
type Child1_T is new Child1_G.GenericChild_T with null record;
end package Child1;
And this is the child1.adb that completes it and works, but I'm not sure if it is a better way to achieve it, such as renaming or something else:
package body Child1 is
procedure P_SetGenericAttribute (Self : in out Child1_T;
i : Integer) is
begin
Child1_G.GenericChild_T(Self).P_SetGenericAttribute (i);
end P_SetGenericAttribute;
function F_GetGenericAttribute (Self : Child1_T)
return Integer is
i : Integer;
begin
i := Child1_G.GenericChild_T(Self).F_GetGenericAttribute;
return i;
end F_GetGenericAttribute;
end package Child1;
Any advice and/or best practices are welcome.
no selector "P_SetGenericAttribute" for private type "Child1_T" ...
The reason you're getting this error is because your implementation (the derivation of the instantiation of GenericChild) is private; your client simply cannot see that the type is such a derivation.
But you have a bigger problem: Ada does not do multiple inheritance like you diagram. You could do multiple interface-types and derive from that, though. OR you could possibly use generic and static polymorphism. -- But straight-up multiple inheritance won't work. (You can, as you mentioned, use mix-ins as well, but those aren't really inheritance.)
I want to create a TService descendant class that I use as the basis for my Windows service implementations. In my base class I am introducing a published ServiceDescription property, and I am using the AfterInstall event handler to write this description to the appropriate location in the Windows registry.
Note that since the TServer class (declared in Vcl.SvcMgr) is a TDataModule descendant, in order to permit the ServiceDescription property to be visible in the Object Inspector it is necessary to declare this base class in a designtime package, and register it with Delphi using a call to RegisterCustomModule. In addition, a descendant of this base class must be generated by an OTA (open tools api) wizard or some sort of code generator (both .pas and .dfm files). No problem, I've got that one sorted, and if you're interested you can read more about it from Marco Cantu's book (http://www.marcocantu.com/ddh/ddh15/ddh15e.htm).
Where I’m stuck is that I want to use the AfterInstall event handler in my base class to write to the Registry, and the AfterUninstall to remove it, but I want to ensure that my descendant classes will also support AfterInstall and AfterUninstall events.
I previously learned from Ray Konopka that if you want to reintroduce a property that you must use accessor methods in the descendant class. As a result, here is a code segment that represents my attempt to do this with respect to the AfterInstall event:
private
// field to store method pointer for the descendant AfterInstall event handler
FFAfterInstall: TServiceEvent;
…
protected
function GetAfterInstall: TServiceEvent;
procedure SetAfterInstall( value: TServiceEvent );
…
published
property AfterInstall: TServiceEvent read GetAfterInstall write SetAfterInstall;
My overridden constructor assigns a method to the inherited AfterInstall property:
constructor TTPMBaseService.Create(AOwner: TComponent);
begin
inherited;
// Hook-up the AfterInstall event handlers
Self.AfterInstall := CallAfterInstall;
…
end;
In my implementation of CallAfterInstall, after I run my code to write to the Windows Registry, I test to see if a method pointer has been assigned to my local method pointer field, and if so, I call it. It looks something like this:
procedure TTPMBaseService.CallAfterInstall(Service: TService);
var
Reg: TRegistry;
begin
// Code here to write to the Windows Registry is omitted
// Test if our method pointer field has been written to
if Assigned( FFAfterInstall ) then
FFAfterInstall( Service ); // call the method,
…
end;
I think that this all makes a lot of sense, and I think it should work. However, I’m stuck on the accessor methods. The Get accessor method compiles just fine, and here it is:
function TTPMBaseService.GetAfterInstall: TServiceEvent;
begin
Result := FFAfterInstall;
end;
But my SetAfterInstall method raises a compile-time exception, reporting that there are not enough parameters:
procedure TTPMBaseService.SetAfterInstall( value: TServiceEvent );
begin
if value <> FFAfterInstall then
FFAfterInstall := value;
end;
I’m not sure what to do here. I made the following change, and it compiles, but it does not appear to do the job:
procedure TTPMBaseService.SetAfterInstall( value: TServiceEvent);
begin
if #value <> #FFAfterInstall then
#FFAfterInstall := #value;
end;
I have two questions. The first is, am I taking the correct approach to reintroducing an event handler, all the while ensuring that both my base class, as well as its descendants, support this event? If my logic is correct, what am I doing wrong with the Setter accessor method?
Am I taking the correct approach to reintroducing an event handler?
Probably yes. Thanks to clumsy design of TService class you're not able to override a method that raises the event.
What am I doing wrong with the Setter accessor method?
The problem is in fact in your constructor:
constructor TTPMBaseService.Create(AOwner: TComponent);
begin
inherited;
// Hook-up the AfterInstall event handlers
Self.AfterInstall := CallAfterInstall;
…
end;
The comment in it indicates that you're setting inherited event handler, but that's not what the code below the comment does. Despite of assigning to Self.AfterInstall you're setting the value of reintroduced property. This is how you set inherited property:
constructor TTPMBaseService.Create(AOwner: TComponent);
begin
inherited;
// Hook-up the AfterInstall event handlers
inherited AfterInstall := CallAfterInstall;
…
end;
But my SetAfterInstall method raises a compile-time exception, reporting that there are not enough parameters.
You're getting syntax error on if statement in the setter method to be precise. It's simply because that's not how you compare method references in Delphi. See How to check if two events are pointing to the same procedure in Delphi. Why do you even need to perform such comparison? You can safely omit it.
I am using newest Delphi 10.3.3
I have several main classes, which are extended from same parent class and also some, let's call it, Reflection classes, which has also same parent. I want instance of main class to have link to corresponding reflection instance, but I run to this issue (I simplified it to this example):
Main classes:
TMainClass = class
Link: TReflectionClass;
end;
TCarMainClass = class(TMainClass)
Link: TCarReflectionClass;
end;
Reflection classes:
TReflectionClass = class;
TCarReflectionClass = class(TReflectionClass);
The problem lies in Link field. I want Link field in TCarMainClass to be directly defined as TCarReflectionClass to avoid type-casting on lot of pieces of code (and also risking some errors), however if I define class like this, TCarMainClass.Link just hides TMainClass.Link - it is defined as different field with same name. That is not good because it consumes extra memory and mainly I can't access that field from parent class, which I want (as pointer to generic instance).
Of course I can solve this by making field private of common type and define property setter/getter which handles re-typing in each class. But that is lot of extra code for these classes and also overhead on each get/set because of calling getter/setter methods.
My question is - is there some easy hack I missed, to tell compiler I want field in child class to occupy same memory location as certain field in parent class?
Thanks
and mainly I can't access that field from parent class
While it's true that parent class can't access the descendant's field, there is nothing preventing you from synchronizing the 2.
procedure TCarMainClass.SetLink(const Value : TCarReflectionClass);
begin
FLink := Value;
TMainClass(Self).FLink := Value;
end;
Now, if you absolutely require no additionnal memory use and no setters, the only remaining option (that I can think of at this time) is as GolezTrol suggested, generics.
TMainClass<T : TReflectionClass> = class
Link: T;
end;
TCarMainClass = class(TMainClass<TCarReflectionClass>)
end;
But that would probably break your design though, because TCarMainClass would be incompatible with TMainClass<TReflectionClass>. (if you want to understand why, search for the terms covariance/contravariance).
I need to limit the scope of a variable to the function it resides with however I need to declare it within an if statement as it's type will change depending. I'm working within VB.NET
Public Function CourseDataTable()
If RadioCourses.Checked Then
Dim SearchBy As New classSearchCourses
ElseIf RadioAttendees.Checked Then
Dim SearchBy As New classSearchAttendees
End If
The obvious problem is that the variable doesn't persist outside of the if statement. I want to limit the scope of this variable because a, it's used else where and b, memory leakage, the class could very well end up holding whole SQL tables and I don't want that persisting when it's not needed.
I can't use inheritance or polymorph here because I'm working a legacy system.
This is probably a rework (I'm struggling think of a different way of approaching it evidently) as I can't find anything in MSDN that allows procedure scope but ignores any other blocks at declaration.
It is still possible to use polymorphism in a legacy system. What you can do is find the common functionality that must exist between the two in order for you to even want to reuse the same variable. Then you can create wrapper classes for each of these legacy classes. The wrapper class would implement the common interface and simply call the underlying legacy implementation. Then you simply declare a variable to that common Interface and create the appropriate wrapper class instance inside of the if statements.
Edit: If you have the ability to modify the legacy classes at all, a simpler solution would be to simply create a common Interface that both of the legacy classes can implement. This will give you the polymorphic functionality that you desire without the need of wrapper classes. VB.Net even provides the ability to implement an interface in a way to where the interface methods are only exposed by a Interface reference. To do this, you simply mark the interface implementation methods as Private.
You could just declare SearchBy as Object and then do something like this
Dim searchBy As Object
If RadioCourses.Checked Then
searchBy = New classSearchCourses
ElseIf RadioAttendees.Checked Then
searchBy = New classSearchAttendees
End If
If searchBy.GetType() Is GetType(classSearchCourses) Then
'Do something
ElseIf searchBy.GetType() Is GetType(classSearchAttendees) Then
'Do something else
End If
This is still inheritance though since most everything inherits from System.Object but it will save you declaring your own new base class if for some reason you can't do that