Filtering Collections in C#

Filtering collections in C#

If you're using C# 3.0 you can use linq, which is way better and way more elegant:

List<int> myList = GetListOfIntsFromSomewhere();

// This will filter ints that are not > 7 out of the list; Where returns an
// IEnumerable<T>, so call ToList to convert back to a List<T>.
List<int> filteredList = myList.Where(x => x > 7).ToList();

If you can't find the .Where, that means you need to import using System.Linq; at the top of your file.

Linq filter from Collection C#

If you only expect one person which status is null you can use First/FirstOrDefault:

Person p = (from p in house.People where p.status == null).FirstOrDefault();

or in method syntax

Person nullStatusPerson = house.People.FirstOrDefault(p => p.status == null);

It will be null if there is no person which status is null.

If you want all persons which status is null you could create a collection:

List<Person> nullStatusPersons =  house.People.Where(p => p.status == null).ToList();

Finally, you can use Single/SingleOrDefault if it was a bug if there were more than one Person which status is null. It will throw an InvalidOperationException then.

How to filter a collection inside a generic method

The easiest way is to ensure that both classes implement a common interface and constrain your generic method. For example:

public interface IProcessable
{
bool isProcessed { get; set; }
}
public class A : IProcessable
{
public int CustID { get; set; }
public bool isProcessed { get; set; }
}

public class B : IProcessable
{
public int EmpId { get; set; }
public bool isProcessed { get; set; }
}

Now your method would look like this:

public void ProceesData<T>(IList<T> param1, string date1)
where T : IProcessable // <-- generic constraint added
{
foreach (var element in param1)
{
element.isProcessed = true;
}
}

Another option which is more useful if you cannot use an interface or the property names vary, is to pass in an Action<T> as a parameter to your method. For example:

public void ProceesData<T>(IList<T> param1, string date1, Action<T> func)
{
foreach (var element in param1)
{
func(element);
}
}

And call it like this:

ProceesData<A>(list, "", x => x.isProcessed = true);

Filter in collection c#

Assuming you're using .NET 3.5 or higher, and you don't need the filtering to be in place, just use LINQ:

var filteredUsers = unfilteredUsers.Where(u => u.UserID < 1 || u.UserID > 100)
.ToList();

Note that this is filtering out users with IDs between 1 and 100 rather than filtering them in as per other answers.

If this doesn't help, please clarify the question. (Out of interest, why are you using Collection<T> to start with?)

EDIT: If you really need a Collection<T> you can create one easily enough:

var filteredUsers = new Collection<User>
(unfilteredUsers.Where(u => u.UserID < 1 || u.UserID > 100)
.ToList());

You could even add your own ToCollection extension method to make this simpler. But Collection<T> is usually meant to be the base class for more specific collection types (e.g. ObservableCollection<T>) - it's odd to be constructing one directly. If your API is written in terms of Collection<T>, you should potentially change it to be written in terms of IList<T>, giving you more flexibility.

Filtering collection with LINQ

You can build a lambda expression to create a proper predicate using the Expression class.

public static Expression<Func<TInput, bool>> CreateFilterExpression<TInput>(
IEnumerable<Filter> filters)
{
ParameterExpression param = Expression.Parameter(typeof(TInput), "");
Expression lambdaBody = null;
if (filters != null)
{
foreach (Filter filter in filters)
{
Expression compareExpression = Expression.Equal(
Expression.Property(param, filter.FieldName),
Expression.Constant(filter.FilterString));
if (lambdaBody == null)
lambdaBody = compareExpression;
else
lambdaBody = Expression.Or(lambdaBody, compareExpression);
}
}
if (lambdaBody == null)
return Expression.Lambda<Func<TInput, bool>>(Expression.Constant(false));
else
return Expression.Lambda<Func<TInput, bool>>(lambdaBody, param);
}

With this helper method, you can create an extension method on any IQueryable<T> class, so this should work for every LINQ backend:

public static IQueryable<T> Where<T>(this IQueryable<T> source, 
IEnumerable<Filter> filters)
{
return Queryable.Where(source, CreateFilterExpression<T>(filters));
}

...which you can call like this:

var query = context.Persons.Where(userFilters);

If you want to support IEnumerable<T> collections as well, you'll need to use this extra extension method:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, 
IEnumerable<Filter> filters)
{
return Enumerable.Where(source, CreateFilterExpression<T>(filters).Compile());
}

Note that this only works for string properties. If you want to filter on fields, you'll need to change Expression.Property into Expression.Field (or MakeMemberAccess), and if you need to support other types than string properties, you'll have to provide more type information to the Expression.Constant part of the CreateFilterExpression method.

Filtering large data collections in a ListView C#

Try using ICollectionView.

xaml

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<TextBox TextChanged="FilterTextChanged" Text="{Binding Path=FilterString, UpdateSourceTrigger=PropertyChanged}"/>
<ListView
x:Name="InfosListView"
Grid.Row="1"
ItemsSource="{Binding Path=Infos}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- List Box Item Layout -->
<StackPanel Orientation="Horizontal">
<Label Content="Text:"/>
<Label Content="{Binding Text}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>

CS

    private void FilterTextChanged(object sender, TextChangedEventArgs e)
{
UpdateFilter();
}

private void UpdateFilter()
{
//NOTE: bellow comment only applies to DataGrids.
//Calling commit or cancel edit twice resolves exceptions when trying to filter the DataGrid.
//https://stackoverflow.com/questions/20204592/wpf-datagrid-refresh-is-not-allowed-during-an-addnew-or-edititem-transaction-m
//CommitEdit();
//CommitEdit();

ICollectionView view = CollectionViewSource.GetDefaultView(Infos);
if (view != null)
{
view.Filter = delegate (object item)
{
if (item is ObjectInfo objectInfo)
{
return objectInfo.Text.Contains(FilterString);
}
return false;
};
}
}

Next upgrade would be to add a DispatcherTimer to the textchanged event so that the filter only updates after text has not been enter for about a second, instead of for each character.

Fastest way to filter a collection C#


Other than these, are there other methods of filtering, such as using different collection types, or filtering functions?

If you can have multiple objects with the same name, you can use a Lookup<string, Foo>. You can think of a lookup as a string -> List<Foo> dictionary:

// create
var foosByName = GetAllObjs().ToLookup(x => x.Name, x => x);

// search
var barFoos = foosByName["bar"].ToList();

Of course, if there is only one Foo for every name, a classic Dictionary<string, Foo> will serve.

Searching in a dictionary or lookup is (usually) an O(1) operation, whereas the search methods in your question are O(n).

Filtering a collection of objects by ID

If I understand your problem correctly, you just need:

PersonList = myPersonList.Where(p => p.PartyGroupID == groupId).ToList();


Related Topics



Leave a reply



Submit