Hello!
Hello!
[UPDATE]
I think I know now what was going on :)
There are several parts to this story:
1) Generic interfaces with GUID
2) A specialized interface WITH a different type as parameter*
3) Other specialized interfaces
TD;LR; It's a compiler bug.
The key to understanding the issue is this:
IEntityList = interface( IBaseEntity )
['{28832807-5D4E-430B-8545-AF6244E923A0}']
procedure Load( List: TRoArray;EstateID: String );
end;
IWebEstateUserList = interface( IEntityList )
procedure Load( var List: TWebEstateUserList;EstateID: String );
end;
The second interface has a var parameter that is declared like so:
Type
TWebEstateUserList = class( TROArray )
end;
The interesting bit is that even if I remove the GUID, the problem remains.
If an implementation class is NOT provided, then the unit compiles just fine.
Another issue is the fact that one Load has a var parameter and the other does not, essentially making them overloaded (even though that was not intended).
This, by the way, is quite likely why it was complaining for a missing implementation :)
Now, just why it would be looking at the earlier class and then also processing it while clearly inside comments that is not something I can explain. But at least we now know what was going on and why it behaved like that :)
[OLD POST]
Not sure what is going on here.
Here are my interfaces:
IBaseEntity = interface
['{95AE6EE1-CDBF-4B4D-BB34-61E5FF5A10CF}']
end;
IDBEntity = interface( IBaseEntity )
['{484D50B8-CFAD-4048-8479-0D424A62E5A3}']
procedure Save( Entity: T );
procedure Load( Entity: T );
end;
IPersistenceRegistry = interface
['{E5DE87BD-6994-4D9C-ACCB-7698601C2D6B}']
procedure Register( EntityPersistence: TInterfacedClass;Entity: TClass );
function FindPersistence( Entity: TClass ) : IBaseEntity;
end;
IWebEstate = interface( IDBEntity )
['{A4A5B3D7-53D8-4923-A4E0-EEC37E3E2C15}']
procedure Save( Entity: TWebEstate );
procedure Load( Entity: TWebEstate );
end;
IWebEstateUser = interface( IDBEntity )
['{96706068-184A-4F89-8464-5BF4771C1848}']
end;
IEntityList = interface( IBaseEntity )
['{28832807-5D4E-430B-8545-AF6244E923A0}']
procedure Load( List: TRoArray;EstateID: String );
end;
IWebEstateUserList = interface( IEntityList )
procedure Load( var List: TWebEstateUserList;EstateID: String );
end;
Then I also have my class declarations (in other units):
Base:
TEntityPersistence = class(TInterfacedObject,IBaseEntity, IDBEntity)
strict protected
procedure Insert( Entity: T );virtual;abstract;
procedure Update( Entity: T );virtual;abstract;
function IdIsEmpty( Entity: T) : Boolean;virtual;abstract;
procedure Save(Entity: T); virtual; abstract;
procedure Load(Entity: T); virtual; abstract;
end;
TEntityList = class( TInterfacedObject, IBaseEntity, IEntityList )
strict protected
procedure Load( List: TRoArray;EstateID : String );virtual;abstract;
end;
Nexus Datasets:
TnxEntityPersistence = class( TEntityPersistence )
strict protected
procedure PrepareSaveParams( Entity: T;AQuery : TnxQuery );virtual;abstract;
public
procedure LoadFromDataset( Entity: T;ADataset: TnxDataset );virtual;abstract;
end;
Concrete classes:
TWebEstatePersistence = class( TnxEntityPersistence,IWebEstate )
strict protected
procedure Insert( Entity: TWebEstate );override;
procedure Update( Entity: TWebEstate );override;
function IdIsEmpty( Entity: TWebEstate) : Boolean;override;
procedure Save(Entity: TWebEstate);override;
procedure Load(Entity: TWebEstate);override;
public
procedure LoadFromDataset( Entity: TWebEstate;ADataset: TnxDataset );override;
end;
TWebEstateUserPersistence = class( TnxEntityPersistence, IWebEstateUser )
strict protected
procedure PrepareSaveParams( Entity: TWebEstateUser;AQuery: TnxQuery );override;
procedure Insert( Entity: TWebEstateUser );override;
procedure Update( Entity: TWebEstateUser );override;
function IdIsEmpty( Entity: TWebEstateUser ) : Boolean;override;
procedure Save(Entity: TWebEstateUser );override;
procedure Load(Entity: TWebEstateUser );override;
public
procedure LoadFromDataset( Entity: TWebEstateUser;ADataset: TnxDataset );override;
end;
TWebEstateUserListPersistence = class( TEntityList, IWebEstateUserList )
strict protected
procedure Load( List : TROArray;EstateID: String );overload;override;
procedure Load( var List: TWebEstateUserList );reintroduce;overload;
end;
For some reason, the compiler complains that TWebEstateUserPersistence does not implement IWebEstateUserList.Load. Now, why does he
think it should? I see no reason for that, but this
doesn't mean there isn't one :)
My Berlin isn't updated (yet).
A
[UPDATE]
I think I know now what was going on :)
There are several parts to this story:
1) Generic interfaces with GUID
2) A specialized interface WITH a different type as parameter*
3) Other specialized interfaces
TD;LR; It's a compiler bug.
The key to understanding the issue is this:
IEntityList
['{28832807-5D4E-430B-8545-AF6244E923A0}']
procedure Load( List: TRoArray
end;
IWebEstateUserList = interface( IEntityList
procedure Load( var List: TWebEstateUserList;EstateID: String );
end;
The second interface has a var parameter that is declared like so:
Type
TWebEstateUserList = class( TROArray
end;
The interesting bit is that even if I remove the GUID, the problem remains.
If an implementation class is NOT provided, then the unit compiles just fine.
Another issue is the fact that one Load has a var parameter and the other does not, essentially making them overloaded (even though that was not intended).
This, by the way, is quite likely why it was complaining for a missing implementation :)
Now, just why it would be looking at the earlier class and then also processing it while clearly inside comments that is not something I can explain. But at least we now know what was going on and why it behaved like that :)
[OLD POST]
Not sure what is going on here.
Here are my interfaces:
IBaseEntity = interface
['{95AE6EE1-CDBF-4B4D-BB34-61E5FF5A10CF}']
end;
IDBEntity
['{484D50B8-CFAD-4048-8479-0D424A62E5A3}']
procedure Save( Entity: T );
procedure Load( Entity: T );
end;
IPersistenceRegistry = interface
['{E5DE87BD-6994-4D9C-ACCB-7698601C2D6B}']
procedure Register( EntityPersistence: TInterfacedClass;Entity: TClass );
function FindPersistence( Entity: TClass ) : IBaseEntity;
end;
IWebEstate = interface( IDBEntity
['{A4A5B3D7-53D8-4923-A4E0-EEC37E3E2C15}']
procedure Save( Entity: TWebEstate );
procedure Load( Entity: TWebEstate );
end;
IWebEstateUser = interface( IDBEntity
['{96706068-184A-4F89-8464-5BF4771C1848}']
end;
IEntityList
['{28832807-5D4E-430B-8545-AF6244E923A0}']
procedure Load( List: TRoArray
end;
IWebEstateUserList = interface( IEntityList
procedure Load( var List: TWebEstateUserList;EstateID: String );
end;
Then I also have my class declarations (in other units):
Base:
TEntityPersistence
strict protected
procedure Insert( Entity: T );virtual;abstract;
procedure Update( Entity: T );virtual;abstract;
function IdIsEmpty( Entity: T) : Boolean;virtual;abstract;
procedure Save(Entity: T); virtual; abstract;
procedure Load(Entity: T); virtual; abstract;
end;
TEntityList
strict protected
procedure Load( List: TRoArray
end;
Nexus Datasets:
TnxEntityPersistence
strict protected
procedure PrepareSaveParams( Entity: T;AQuery : TnxQuery );virtual;abstract;
public
procedure LoadFromDataset( Entity: T;ADataset: TnxDataset );virtual;abstract;
end;
Concrete classes:
TWebEstatePersistence = class( TnxEntityPersistence
strict protected
procedure Insert( Entity: TWebEstate );override;
procedure Update( Entity: TWebEstate );override;
function IdIsEmpty( Entity: TWebEstate) : Boolean;override;
procedure Save(Entity: TWebEstate);override;
procedure Load(Entity: TWebEstate);override;
public
procedure LoadFromDataset( Entity: TWebEstate;ADataset: TnxDataset );override;
end;
TWebEstateUserPersistence = class( TnxEntityPersistence
strict protected
procedure PrepareSaveParams( Entity: TWebEstateUser;AQuery: TnxQuery );override;
procedure Insert( Entity: TWebEstateUser );override;
procedure Update( Entity: TWebEstateUser );override;
function IdIsEmpty( Entity: TWebEstateUser ) : Boolean;override;
procedure Save(Entity: TWebEstateUser );override;
procedure Load(Entity: TWebEstateUser );override;
public
procedure LoadFromDataset( Entity: TWebEstateUser;ADataset: TnxDataset );override;
end;
TWebEstateUserListPersistence = class( TEntityList
strict protected
procedure Load( List : TROArray
procedure Load( var List: TWebEstateUserList );reintroduce;overload;
end;
For some reason, the compiler complains that TWebEstateUserPersistence does not implement IWebEstateUserList.Load. Now, why does he
think it should? I see no reason for that, but this
doesn't mean there isn't one :)
My Berlin isn't updated (yet).
A
Nicholas Ring, the compiler highlights TWebEstateUser and not TWebEstateUserList.
ReplyDeleteYour IWebEstateUserList has the following method:
ReplyDeleteprocedure Load( var List: TWebEstateUserList;EstateID: String );
While your implementation class doesn't not have a method with this signature:
procedure Load( List : TROArray;EstateID: String );
procedure Load( var List: TWebEstateUserList );
The first method, it doesn't have the correct first parameter type, and the second method, while the first parameter is correct, it doesn't have the second parameter.
Possible solution: change the second method, by adding a second parameter, to be:
procedure Load( var List: TWebEstateUserList; EstateID: String );
Andrea Raimondi Sorry - I was totally wrong with my first comment and I deleted it...
ReplyDeleteAndrea Raimondi You won't believe it but I am dealing with a very similar situation here :-) It's like I am looking at my code ....haha. In my code, the "strict" parameter confused the compiler. Can you try to remove it or move the Load method to public?
ReplyDeleteJohn Kouraklis moving both to public doesn't help. WTF?
ReplyDeleteAndrea Raimondi Looking at the code more thoroughly and if I don't miss anything, the TWebEstateUserPersistence brings the IDBEntity interface twice in the class and hence the confusion. I think that both Tnx.... and IwebEstateUser descent and carry IDBEntity interface
ReplyDeleteJohn Kouraklis, this is interesting: if I DELETE the IWebEstateUser interface ENTIRELY (AND remove it from the class declaration) then the message does not appear. IF I reinstate the interface BUT NOT reinstate the declaration, the message appears again. This must be a compiler problem.
ReplyDeleteAndrea Raimondi I think this is the problem. You have two different classes/interfaces descending from the same interface and because there are different GUIDs the compiler is expecting to meet the requirements for both classes/interfaces
ReplyDeleteGeneric support is just broken with interfaces if you want to resolve a particular type via its guid. You need to explicitly create one interface type and guid per each specialized methods set.
ReplyDeleteSo, I decided to entirely remove the TWebEstateUserPersistence declaration AND implementation.
ReplyDeleteRebuilt the project and the compiler is STILL stopping on its declaration (yes, IT IS COMMENTED).
So, the compiler is trying to compile commented code. WHAT THE BLOODY HELL!!?!?!!!
A. Bouchez "Generic support is just broken with interfaces"...
ReplyDeleteI totally disagree! The issue with the code above is generic interfaces should NOT have a GUID because one is generated automatically by the compiler when a specific interface type is created from the generic interface (even implicitly, if I remember correctly).
So there is indeed a compiler bug, which is not raising a warning (or error) if you place a GUID after a generic interface, as that is a mistake.
But overall it looks to me the compiler is handling this better than some developers thing -- which is a documentation issue, not a compiler one.
Marco Cantù Do you mean that the IBaseEntity in the code above should not have a GUID?
ReplyDeleteAndrea Raimondi I get this kind of behavior sometimes (especially with breakpoints). Save the project, clean it and then build it and...hopefully...it will work. :-)
ReplyDeleteJohn Kouraklis IBaseEntity is a regular interface, needs a GUID. IDBEntity and IEntityList are generic interfaces, they should not have a GUID, although I'm not 100% sure happens if they do.
ReplyDeleteMarco Cantù I see. You mean an interface with generics. Sorry I misunderstood. So, you are saying that this may confuse compiler and the correct course of action is not to use GUIDs in such cases
ReplyDeleteMarco Cantù Even you are not 100% sure what happens if they do have a GUID? What is sure is that you can't resolve the GUID at runtime, as I wrote. Sounds like a bit broken, to me, at least. ;)
ReplyDeleteOk, he may not remember it off the top of his head now.
ReplyDeleteI posted an update.
ReplyDeleteAndrea Raimondi So, we are certain now that interfaces with generics SHOULD NOT have GUIDs? I need to update my classes in that case
ReplyDeleteJohn Kouraklis I would say that given the current information that is very likely so if you ALSO have other weird/odd things in your code. I can't prove that GUIDs alone created that mess :) But given that Marco Cantù and Malcolm Groves both agree that it's a bad idea, I would update the classes if I were you :)
ReplyDeleteI can't comment on whether the guids caused any or all of Andrea's problems. Seems unlikely somehow.
ReplyDeleteHowever, if you accept that a guid on an interface is meant to uniquely identify that interface, and if you go one step further and accept that IFoo and IFoo are different interfaces, then they shouldn't have the same guid.
Should Delphi figure it out for me under the covers so I don't have to worry? That'd be great, but personally there are things I'd have higher on my generics wishlist than that :-)
/sub
ReplyDeleteAndrea Raimondi I will do this change I think although I haven't noticed any problems because of this. I, also, agree with Malcolm Groves that in your code, the GUIDs do not seem very likely to have caused the behaviour you observed
ReplyDeleteMalcolm Groves That's the question Malcolm: does the compile understand IFoo and IFoo as two different interfaces? Is this documented anywhere even the guidance not to provide a GUID with generic types?
ReplyDeleteMalcolm Groves I am openly saying that they are likely a contributor but you ALSO need other things "wrong". In other words, it may have worked with the GUIDs if there weren't other things as well. I also openly state that I can't prove this was a marking of GUIDs alone. However, I agree under your reasoning (which makes a lot of sense) that generic interfaces should not have a GUID. I'd love if Delphi gave a compilation error if your generic interfaces have GUIDs.
ReplyDelete