[fpc-devel] Allow object type property getter to expose a field as one of its base/parent types (patch included)

Kostas Michalopoulos badsectoracula at gmail.com
Tue Aug 11 21:48:56 CEST 2020

Any further thoughts on this? I am already using it in a new project for
quite some time now and it has proven to be very useful in several cases by
exposing read only views of the same container that is used internally by
classes without additional boilerplate and with fast access and rtti

TBH I've found it so useful that even if it isn't included I'll maintain
the patch myself (it is tiny and the relevant code doesn't seem to be
modified frequently anyway :-P), but I think it is a very useful feature
for everyone because of what it enables you to do (see example in my last
message). Not having it feels like going back to pre-generics FP or
pre-dynamic arrays Delphi; sure, you do not need the functionality, but it
is better when the compiler enables you to avoid busywork and let you focus
on more important parts :-P


On Sun, Jun 14, 2020, 04:00 Kostas Michalopoulos <badsectoracula at gmail.com>

> On Sat, Jun 13, 2020 at 12:07 PM Sven Barth via fpc-devel
> <fpc-devel at lists.freepascal.org> wrote:
> > I'm against it. It would essentially mean that you can't use the same
> > field for the setter or that you'd need to go through a method.
> > Exchanging one restriction (can't use a base class for the property) for
> > another (can't use the same field for the setter) isn't worth it
> > especially as you can avoid performance issues by using a getter method
> > that is declared inline (the only case where this won't help is if you
> > access it using RTTI, but one call more or less doesn't really change
> > much anyway).
> These two restrictions are not equal, using a setter method is much
> more common than using a getter that accesses the field directly. Also
> there is no exchange since you cannot use a property with a base type
> as a setter for the base field. None of this functionality already
> exists, this patch does not introduce any new restriction, it only
> removes an existing one (which is even inconsistent since in other
> places you can use values as their base type when the base type is
> needed).
> Performance-wise inlining doesn't always work (more often than not the
> compiler fails to inline methods, especially when nesting inlines) and
> so it cannot be relied upon. In addition, using a getter method does
> change the RTTI data which is a significant difference.
> To give you an idea on how i'm using this, i have a generic object
> type (not class) that provides a read only view of a collection and a
> mutable view that extends it, like this:
> generic TDynArrayView<T> = object ...
> generic TDynArray<T> = object(specialize TDynArrayView<T>) ...
> This allows me to use a property like
> property Items: specialize TDynArrayView<T> read FItems; // FItems is
> TDynArray
> that always provides direct access to the items but without the API
> allowing any modifications that ensures all modifications to the
> collection itself will be done through the methods of the class that
> provides the property but all accesses will have minimum overhead.
> The RTTI data is also important because i have a custom serialization
> system that tries to automate as much as possible with minimal
> hand-holding (i also use custom attributes for this). All objects are
> assumed to descent from a TRootObject object that provides functions
> like SerializeToStream/DeserializeFromStream and this does deep
> de/serialization (ie. serializes all objects, not just just the root
> one).
> This takes advantage of getter-only field-access properties to assume
> that they are meant to also be serialized and owned by the object
> itself (which is a safe assumption for most objects, custom attributes
> can be used to change the behavior) so when deserializing, it writes
> to those getter-only field-access properties directly. The implication
> of this is that the address of the field must be available and thus an
> getter function wouldn't work (before doing the patch i did try to
> take the offset of the field variable and store it in a custom
> attribute as a workaround, but it didn't work since parameters to
> custom attributes do not seem to be able to do that - and besides i'm
> not a fan of this since it adds extra noise to the declaration and
> custom attributes seem to be unable to have any statically allocated
> data and you need to allocate new instances just to access their data
> - though that is another issue that i have with custom attributes in
> general).
> What the above means is that i can have two objects like
> TSomething = class(TRootObject) ...
> TAnother = class(TRootObject)
> private
>   FSomething: TSomething;
> published
>   property Something: FSomething read FSomething;
> end;
> and then when i do Another.SerializeToStream(SomeStream) and
> Another.DeserializeFromStream(SomeStream) i can have the TSomething
> instance automatically be stored and recreated without any additional
> hand-holding boilerplate code (you need to provide a default Create
> constructor, but this is basically the same requirement like
> TPersistent). I also have a THidden custom attribute which is used to
> mark published properties that are not to be exposed to the UI.
> Also note that i use both of the above cases with classes like
> generic TItemCollectionView<T> = class(TRootObject) ...
> generic TItemCollection<T> = class(specialize TItemCollectionView<T>) ...
> Which allows me to automatically de/serialize collections owned by
> objects without any additional custom code (these two classes actually
> wrap the TDynArray generic objects i mentioned above).
> So basically as you see not only inline functions aren't equivalent
> (they change the RTTI data, they can potentially be slower -
> especially when considering value types like objects) but i'm actually
> having two real use cases for this functionality.
> I cannot think of a use case for the restriction you mention about
> allowing to write to the setter (and again, that wouldn't make sense
> logically either - you can "get" a base type value from a derived type
> value, but nowhere in the language you can "put" a base type value to
> a derived type value since that base type might be some other derived
> type that doesn't match).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freepascal.org/pipermail/fpc-devel/attachments/20200811/08c27213/attachment.htm>

More information about the fpc-devel mailing list