Skip to content

Expose TDLib object vectors as List<T> - #3366

Draft
FrayxRulez wants to merge 3 commits into
developfrom
tdlib-list-t
Draft

Expose TDLib object vectors as List<T>#3366
FrayxRulez wants to merge 3 commits into
developfrom
tdlib-list-t

Conversation

@FrayxRulez

Copy link
Copy Markdown
Collaborator

Generated TDLib objects expose their vectors as List<T> instead of IList<T>. Functions are
left alone.

Why

A XAML binding assigns through the declared type, so with IList<T> the concrete type only exists
at runtime and nothing can see it at compile time. That is how SettingsStoragePage came to bind
Statistics.ByChat to ItemsSource and fail with E_INVALIDARG, with no warning anywhere. With
List<T> the type is visible in the generated binding code, and the analyzer reports it.

Two more reasons, both on paths that run per render:

  • foreach over an IList<T> boxes the enumerator. List<T> has a struct one. .Entities alone is
    iterated in 23 places.
  • The indexer and Count inline instead of dispatching through the interface.

Functions gain none of this - they are only ever written and serialised, never bound or iterated -
and keeping IList<T> there leaves 93 call sites able to pass an array. So they keep it.

Shape

  • Object properties and constructor parameters are List<T>; nested vectors are List<List<T>>.
  • Both parsers already materialised a vector as a List<T>, so nothing changes at runtime for a
    parsed response. The nested readers now build List<List<T>> and the WriteArray overload for
    them was widened to match.
  • Call sites moved from new[] { x } and Array.Empty<T>() to [x] and [], which allocates
    less than the intermediate step did - the converter copied every array into a list.

Worth a look in review

  • TryGetColors and the two chat-list readers fill by Add rather than by index. A List's
    capacity is not its count, so the literal translation of new T[n] would throw.
  • TextStyleRun.NoEntities and AnimatedImageSource.NoOutline are shared empty lists, because
    Array.Empty<T>() was free and these sit on the render path. A shared mutable list is a
    footgun: IList<T>.Add on an array used to throw, and now it silently corrupts a singleton.
    Inheriting a frozen List<T> cannot help - the members are not virtual, so the guard is inert
    whenever the static type is List<T>, which it always is here. The plan is to return a fresh list
    from GetEntities, whose result callers may edit, and to add a debug-only check that the count is
    still zero.
  • MessageServiceText.ReplaceWithLink loses an if (Entities.IsReadOnly) copy-guard that only
    existed because an IList<T> could be a read-only array wrapper.
  • Three foreach (… in x.Reverse()) became index loops: List<T>.Reverse() is in-place and returns
    void, so they no longer compile as written. The one bare x.Reverse(); statement in the codebase
    is on a different type and is unaffected.

Builds clean. Not run.

Note on the diff

This stacks on three commits that are on develop locally but not yet pushed, so the PR currently
shows six. The first three drop out once develop is pushed.

FrayxRulez and others added 3 commits August 17, 2026 13:42
Objects are read - bound to ItemsSource, iterated per render - so List makes
the concrete type visible to the analyzer, stops foreach boxing an enumerator
and lets the indexer inline. Functions are only ever written and serialised,
and keep IList. Constructors take IList either way, so arrays and collection
expressions still bind, and a parsed response is cast rather than copied.

AnimatedImage draws no shimmer when Outline is null, hence the shared empty.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A binding does not assign the property: it calls a Set_ on XamlBindingSetters,
which takes the value as object and belongs to this assembly, so nothing
flagged it. Following that one call reports 83 collections bound from XAML
with no CCW vtable - each one a page that throws when opened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parameter and the field are the same type now, so TdCollection.AsList is
gone. Call sites became [x] and [], which allocates less than it did: the
converter copied every array into a list.

Four needed more than a rewrite. TryGetColors and the two chat-list readers
fill by Add rather than by index, because a List's capacity is not its count.
TextStyleRun shares one empty list, since Array.Empty was free and these sit
on the render path - a shared mutable list is a footgun, and notes has the
plan for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@FrayxRulez
FrayxRulez marked this pull request as draft August 17, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant