dotnet/maui · error · NotSupportedException

DataTemplateSelector.OnSelectTemplate must not return anothe

Error message

DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector

What it means

DataTemplateSelector.SelectTemplate calls the overridden OnSelectTemplate and then rejects the result if it is itself a DataTemplateSelector (NotSupportedException). Nested selectors are forbidden because SelectTemplate caches per-type templates and the recycling path assumes a concrete DataTemplate; allowing a selector to return a selector would create unbounded recursion and break caching.

Source

Thrown at src/Controls/src/Core/DataTemplateSelector.cs:34

		/// <param name="container">The bindable object that will display the templated item.</param>
		/// <returns>The selected <see cref="DataTemplate"/>.</returns>
		public DataTemplate SelectTemplate(object item, BindableObject container)
		{
#pragma warning disable CS0618 // Type or member is obsolete
			var listView = container as ListView;
#pragma warning restore CS0618 // Type or member is obsolete

			var recycle = listView == null ? false :
				(listView.CachingStrategy & ListViewCachingStrategy.RecycleElementAndDataTemplate) ==
					ListViewCachingStrategy.RecycleElementAndDataTemplate;

			DataTemplate dataTemplate = null;
			if (recycle && _dataTemplates.TryGetValue(item.GetType(), out dataTemplate))
				return dataTemplate;

			dataTemplate = OnSelectTemplate(item, container);
			if (dataTemplate is DataTemplateSelector)
				throw new NotSupportedException(
					"DataTemplateSelector.OnSelectTemplate must not return another DataTemplateSelector");

			if (recycle)
			{
				if (!dataTemplate.CanRecycle)
					throw new NotSupportedException(
						"RecycleElementAndDataTemplate requires DataTemplate activated with ctor taking a type.");

				_dataTemplates[item.GetType()] = dataTemplate;
			}

			return dataTemplate;
		}

		protected abstract DataTemplate OnSelectTemplate(object item, BindableObject container);
	}
}

View on GitHub (pinned to f377ff1c5e)

Solutions

  1. Make OnSelectTemplate return a concrete DataTemplate (or null), never a DataTemplateSelector.
  2. If you want composition, call the inner selector's SelectTemplate (or OnSelectTemplate) inside your override and return the resolved DataTemplate it yields.
  3. Cache concrete DataTemplate instances as fields and return those.

Example fix

// before
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    => item is TypeA ? _otherSelector : _templateB; // _otherSelector is a DataTemplateSelector -> throws

// after
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    => item is TypeA ? _otherSelector.SelectTemplate(item, container) : _templateB;
Defensive patterns

Strategy: validation

Validate before calling

// Guard OnSelectTemplate implementations to never return a selector.
static DataTemplate SafeSelect(DataTemplate result)
{
    if (result is DataTemplateSelector)
        throw new ArgumentException("OnSelectTemplate returned a DataTemplateSelector; resolve to a concrete DataTemplate first.");
    return result;
}

Type guard

static bool IsConcreteDataTemplate(DataTemplate t) => t is not DataTemplateSelector;

Prevention

When it happens

Trigger: A custom DataTemplateSelector whose OnSelectTemplate returns an instance of another DataTemplateSelector subclass; delegating selection to a composed selector and returning that selector object rather than its resolved DataTemplate.

Common situations: Refactoring a single large selector into multiple selectors and accidentally returning the sub-selector; intent to compose selection logic by chaining selectors.

Related errors


AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13). Data as JSON: /api/errors/119bfdb7b3171970. Report an issue: GitHub.