Thursday, April 27, 2023

LINQ OfType - Converting an IEnumerable to IEnumerable<Type>

Some collections such as ItemCollection implement IEnumerable but do not implement IEnumerable<Type>, DataTable.Rows is another example. If you want to use LINQ extensions such as Any, All, First, etc. you can use Cast<Type> or OfType<Type> to convert them to an IEnumerable<Type>. Then you can use the LINQ extensions.

Here is an example. Start a new Visual Basic project called OfType. Make it a WPF C# application.

We will create a context menu, then use LINQ to see if it contains a particular menu item when the menu is opened. Here is the XAML and code behind without the conversion to IEnumerable<Type>. You can see it will not build.

<Window x:Class="OfType.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OfType"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        ContextMenuOpening="Window_ContextMenuOpening"
        Title="MainWindow" Height="450" Width="800">
    <Window.ContextMenu>
        <ContextMenu>
            <MenuItem Header="One"/>
            <MenuItem Header="Two"/>
            <MenuItem Header="Three"/>
        </ContextMenu>
    </Window.ContextMenu>
</Window>


using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace OfType
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Window_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            Window w = sender as Window;
            ContextMenu cm = w.ContextMenu as ContextMenu;
            Boolean Exists = false;
 
            Exists = cm.Items.Any(mi => mi.Header.ToString() == "Two");
            Console.WriteLine("Menu item 'Two' does " + (Exists ? "":"not ") + "exist");
        }
    }
}

There are two ways to fix the problem. Cast<Type> and OfType<Type>. The Cast method will raise an InvalidCast exception if any of the members of the collection cannot be cast. The OfType method will only return the members that can be cast. I will use OfType. Replace the line starting with Exists.

Exists = cm.Items.OfType<MenuItem>().Any(mi => mi.Header.ToString() == "Two");

You can now run the application and see the output in the Immediate window.

Menu item 'Two' does exist

No comments:

Post a Comment