How can i make a typecast using the object classType parameter?
(obj as obj.classType).items[i]...//obj.classType = TList<myType>
This code is rejected by the compiler. But, moreover, I need to get access to the properties of the object, no matter what class (TList) it possesses:
system.classes.TList
system.generics.collections.TList<T>
In my program there is an object that actually belongs to the class TList<T>, and I am afraid that casting to a system.classes.TList type may cause some errors in my program.
It's impossible to typecast like this. That is because Delphi is a statically typed language.
Imagibe you could write code like this:
(obj as obj.classType).items
Since the value if obj.classType is unknown at compile time, the compiler cannot, at compile time, know whether items even exists, never mind how to access it and so on.
As for your goal regarding list classes, the generic and non-generic list types do not share a common ancestor beyond TObject. What you are attempting is simply impossible.
Whatever your problem is, you'll need to find a different solution.
Related
We have an app that makes fairly extensive use of TIniFile. In the past we created our own descendant class, let's call it TMyIniFile, that overrides WriteString. We create one instance of this that the entire app uses. That instance is passed all around through properties and parameters, but the type of all of these is still TIniFile, since that is what it was originally. This seems to work, calling our overridden method through polymorphism, even though all the variable types are still TIniFile. This seems to be proper since we descend from TIniFile.
Now we are making some changes where we want to switch TMyIniFile to descend from TMemIniFile instead of TIniFile. Those are both descendants of TCustomIniFile. We'll also probably be overriding some more methods. I'm inclined to leave all the declarations as TIniFile even though technically our class is no longer a descendant of it, just to avoid having to change a lot of source files if I don't need to.
In every tutorial example of polymorphism, the variable is declared as the base class, and an instance is created of the descendant class and assigned to the variable of the base class. So I assume this is the "right" way to do it. What I'm looking at doing now will end up having the variables declared as, what I guess you'd call a "sibling" class, so this "seems wrong". Is this a bad thing to do? Am I asking for trouble, or does polymorphism actually allow for this sort of thing?
TIniFile and TMemIniFile are distinct classes that do not derive from each other, so you simply cannot create a TMemIniFile object and assign it to a TIniFile variable, and vice versa. The compiler won't let you do that. And using a type-cast to force it will be dangerous.
You will just have to update the rest of your code to change all of the TIniFile declarations to TCustomIniFile instead, which is the common ancestor for both classes. That is the "correct" thing to do.
The compiler is your friend - why would you lie to it by using the wrong type ... and if you do lie to it why would you expect it to know what you want it to do?
You should use a base class that you derive from, like TCustomIniFile. I would expect compile issues if you are trying to make assignments which are known at compile time to be wrong.
The different classes have different signatures so the compiler needs to know which class it is using to call the correct method or access the correct property. With virtual methods the different classes setup their own implementation of those methods so that the correct one is called - so using a pointer to a base type when you call the virtual method it calls that method in the derived type because it is in the class vtable.
So if the code does compile, it's very likely that the compiler will not be doing the right thing ...
What is the difference in casting using Supports vs as keyword, besides that with as I first need to check if cast is possible with is keyword.
Supports will provide an interface if that interface is supported.
is determines if a class/interface derives from another class/interface.
as does the same thing as is, but also returns a type-checked cast.
If you have already confirmed with is that a cast will succeed, you don't need to use as, you can just do a direct cast, which is more efficient:
if(pSomeObjectRef is TMyObject) then
TMyObject(pSomeObjectRef).MyUsefulMethod(...);
As Delphi does not support multiple inheritance, using interfaces is the only way to implement that kind of behavior. An object which can be more than one thing, not just itself or its ancestors.
If you're not using interfaces, you shouldn't need to use Supports().
Using as to cast allows you to cast an object reference to an interface, as well as to a reference to a different class of object. Personally, I don't use as, and I rarely see it in the code I'm looking at. Since as can raise an exception, you ought to take steps to avoid the exceptions, and catch them if they are raised. As you can check these anyway, there should never be a need to use as.
When casting to an interface, rather than relying on the exception to catch the interface not being there, you can use the result of Supports():
if (SysUtils.Supports(pSomeObjectRef, IMyWantedInterface, diInterfaceRef)) then
begin
diInterfaceRef._AddRef(); // removed when diInterface falls out of scope
...
end
else
begin // it doesn't support the interface!
...
end;
Whether you want to catch exceptions (which some people like, some people don't - it does make the code less linear), or code for if..else, is usually a matter of preference. I prefer not to rely on exceptions (but I still have try..finally or try..except blocks), but of course other opinions are available!
Just to make sure I'm not overlooking a strange edge case as I've found yet a case that produce it, but I want to make sure:
Is there any Delphi version that can emit RTTI containing a type that has tkUnknown as TTypeKind?
If so:
any documentation reference?
what type would produce it?
In the current Delphi XE5 RTL, the only place I could find handling tkUnknown is TValue, but I've not found a code path in the RTL that sets up a TValue containing a TypeInfo having tkUnknown as Kind.
The answer is no. Anything else would be a bug in the compiler.
tkUnknown is the indication that there is no type info available which might be the case for discontiguous enumerations and enumerations which don't start at zero (as explained by Barry here) and some types from long ago (like Real48).
It also is returned by TValue.Kind when TValue.IsEmpty is true. (since XE2 afaik before it also could return True in cases where it held a reference type that was nil which was a bug).
When you are retrieving RTTI for something that does not contain type info (like a field, property or parameter of a type that has no type info) your RTTI information is incomplete. TRttiField.FieldType and TRttiProperty.PropertyType return nil in these cases and the array returned by TRttiMethod.GetParameters is incomplete.
While it is possible to call TValue.Make<T> with a type that has no type info you will not be able to do much with this because its TypeInfo will be nil. The compiler obviously works around E2134 and passes nil to TValue.Make. Thus TValue.Kind will say tkUnknown.
I have a list of type TList<TForm>. I need to cast it and use it as TList<TObject> like this:
procedure mainForm.testCast;
var
listT: TList<TForm>;
listW: TList<TObject>;
obj: TObject;
begin
listT := TList<TForm>.create;
listT.add(form1);
listT.add(form2);
listW := TList<TObject>(listT); // Casting is OK
// This works, but is this fine?
for obj in listW do
memo1.lines.add(obj.className);
end;
The sample works as expected, but is it ok to cast like this between generic lists? Will this cause some data structure corruption etc?
I use it only for looping (DoGetEnumerator) purposes and some string checks i.e. I'll not add/remove items.
The real function is little more complicated. It gets reference to listT using RTTI in a TValue.
The main goal is not to link FMX.Forms in my unit.
Update:
Why are TGeneric<Base> and TGeneric<Descendant> incompatible types?
Well, your code will work, but it somewhat dubious in my view. Simply put the cast is not legal because
TList<TForm>.InheritsFrom(TList<TObject>)
is false. So a TList<TForm> object is not a TList<TObject>. If it were, then the cast would not be needed.
That this is so is because Delphi's generic types are invariant. More details can be found here:
Why is a class implementing an interface not compatible with the interface type when used in generics?
If you have any difficulty understanding why the designers made generic types invariant, consider for a moment the effect of writing listW.Add(TObject.Create) in your code. Think what it means to the true underlying object of type TList<TForm>.
So the language promises you nothing. You are venturing outside its guarantees. It so happens that the implementation of these two un-related types is compatible enough for your code to work. But that is really just an accident of implementation.
Since you are already using RTTI, then I suggest that you iterate over the list with RTTI. You can call GetEnumerator and so on using RTTI. That way you will call the actual methods of the object.
I want to implement a function in a dll that accepts a record as a parameter and this record as a few fields that hold pointers to callback routines. Would this be safe?
Yes, it's perfectly safe to have a pointer to a record that holds other pointers.
Your title mentions methods, though. DLLs rarely request method pointers because method pointers only exist in Delphi and C++ Builder. DLLs written expecting other tools to be able to use them will request ordinary function pointers, so please beware that method pointers are not compatible with pointers to standalone subroutines. The compiler will usually balk if you try to mix them, but type-casting can quell that error message. As a rule of thumb, if you're type-casting a function pointer or method pointer, you're doing something wrong. If your type declarations and your function declarations are correct, you won't need to type-cast.
Likewise, if you're using the # operator to create a function pointer or method pointer, you're probably doing it wrong. In most cases, the compiler can detect and assign compatible code pointers automatically, without you telling it that there's a pointer. Using # may suppress some of Delphi's type checking.
I don't see why not. I think all the usual issues with procedure/method pointers apply, so the object needs to exist if it's a method pointer.
It is safe but there are two issues that you should be aware about :
Records that declared as local variables are stored in the stack and they go away when the function returns. You should consider to allocate/dispose them on the heap with new/dispose functions.
If the DLL will be used by a program developed in other than delphi (or maybe even different versions of delpi), you have to use packed records.