Back to Devexpress

Custom Aggregate Functions

xtrareports-403889-feature-guide-to-devexpress-reports-use-expressions-custom-aggregate-functions.md

latest9.1 KB
Original Source

Custom Aggregate Functions

  • Feb 18, 2026
  • 5 minutes to read

This topic describes how to implement a custom aggregate function and includes an example of the custom CountDistinct function.

Implement a Function

Define a class that implements the ICustomAggregate interface to create an aggregate that accepts a collection of values, uses an expression or multiple expressions to evaluate the values, and returns a single result.

To use a custom aggregate function in the Expression Editor, implement the ICustomAggregateBrowsable interface, which is an ICustomAggregate descendant. This interface allows you to add a function description displayed in the Expression Editor, and validate the number and type of operands.

Register a Function

Register at Runtime

To enable a custom aggregate function, register it in the application with the CriteriaOperator.RegisterCustomAggregate(ICustomAggregate) method at application startup. It makes the registered custom aggregate function available across the entire application in all locations where the CriteriaOperator is available.

The following code registers the CountDistinctCustomAggregate function:

csharp
DevExpress.Data.Filtering.CriteriaOperator.RegisterCustomAggregate(new CountDistinctCustomAggregate());
vb
DevExpress.Data.Filtering.CriteriaOperator.RegisterCustomAggregate(New CountDistinctCustomAggregate())

Register in Visual Studio Report Designer

Place an attribute on a custom function to enable it in the Visual Studio Report Designer. Use the following attribute:

VSDesignerCustomFunctionAttributeRegisters a custom function in the Visual Studio Report Designer. You can specify the VSDesignerCustomFunctionScope attribute argument to define the registration scope in the Visual Studio Report Designer.

Unregister a Function

When a custom aggregate function is no longer needed or needs to be hidden from the user interface, you can remove it from the application using one of the methods below:

Unregistered functions are not available in the Expression Editor. An expression that has unregistered functions is evaluated as an empty value.

Function Call Syntax

To use a custom aggregate function, you can call it directly:

[CollectionProperty][].MyCustomAggregate(operands)

Alternatively, you can pass the custom aggregate function’s name as one of the parameters:

[CollectionProperty][].AGGREGATE('MyCustomAggregate', operands)

An aggregate is considered top-level if it is applied to the parent collection. If a custom aggregate is top-level, use the following syntax:

  • [].MyCustomAggregate(operands)

  • [].AGGREGATE('MyCustomAggregate', operands)

Example - CountDistinct Function

The following code implements the CountDistinct function that counts the number of distinct values in the specified collection or data field:

csharp
using DevExpress.Data.Filtering;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

public class CountDistinctCustomAggregate : ICustomAggregateBrowsable
{
    static readonly CountDistinctCustomAggregate instance = new CountDistinctCustomAggregate();
    public static void Register() {
        CriteriaOperator.RegisterCustomAggregate(instance);
    }
    public static void Unregister() {
        CriteriaOperator.UnregisterCustomAggregate(instance);
    }
    public string Name { get { return nameof(CountDistinct); } }

    public int MinOperandCount => 1;

    public int MaxOperandCount => 1;

    public string Description => "This is a custom aggregate function";

    Type ICustomAggregate.ResultType(params Type[] operands) {
        return typeof(int);
    }
    object ICustomAggregate.CreateEvaluationContext() {
        return new HashSet<object>();
    }
    bool ICustomAggregate.Process(object context, object[] operands) {
        var ctx = (HashSet<object>)context;
        ctx.Add(operands[0]);
        return false;
    }
    object ICustomAggregate.GetResult(object context) {
        var ctx = (HashSet<object>)context;
        return ctx.Count;
    }

    public static object CountDistinct<T>(IEnumerable<T> collection, Expression<Func<T, object>> arg) {
        throw new InvalidOperationException("This method should not be called explicitly.");
    }

    public bool IsValidOperandCount(int count) {
        return true;
    }

    public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
        return true;
    }
}
vb
Imports System.Linq.Expressions
Imports DevExpress.Data.Filtering

Public Class CountDistinctCustomAggregate
    Implements ICustomAggregateFormattable, ICustomAggregateBrowsable

    Private Shared ReadOnly instance As New CountDistinctCustomAggregate()
    Public Shared Sub Register()
        CriteriaOperator.RegisterCustomAggregate(instance)
    End Sub
    Public Shared Sub Unregister()
        CriteriaOperator.UnregisterCustomAggregate(instance)
    End Sub
    Public ReadOnly Property Name() As String Implements ICustomAggregate.Name
        Get
            Return NameOf(CountDistinct)
        End Get
    End Property

    Public ReadOnly Property MinOperandCount() As Integer Implements ICustomAggregateBrowsable.MinOperandCount
        Get
            Return 1
        End Get
    End Property

    Public ReadOnly Property MaxOperandCount() As Integer Implements ICustomAggregateBrowsable.MaxOperandCount
        Get
            Return 1
        End Get
    End Property

    Public ReadOnly Property Description() As String Implements ICustomAggregateBrowsable.Description
        Get
            Return "This is a custom aggregate function"
        End Get
    End Property

    Private Function ICustomAggregate_ResultType(ParamArray ByVal operands() As Type) As Type Implements ICustomAggregate.ResultType
        Return GetType(Integer)
    End Function
    Private Function ICustomAggregate_CreateEvaluationContext() As Object Implements ICustomAggregate.CreateEvaluationContext
        Return New HashSet(Of Object)()
    End Function
    Private Function ICustomAggregate_Process(ByVal context As Object, ByVal operands() As Object) As Boolean Implements ICustomAggregate.Process
        Dim ctx = DirectCast(context, HashSet(Of Object))
        ctx.Add(operands(0))
        Return False
    End Function
    Private Function ICustomAggregate_GetResult(ByVal context As Object) As Object Implements ICustomAggregate.GetResult
        Dim ctx = DirectCast(context, HashSet(Of Object))
        Return ctx.Count
    End Function
    Private Function ICustomAggregateFormattable_Format(ByVal providerType As Type, ParamArray ByVal operands() As String) As String Implements ICustomAggregateFormattable.Format
        Return String.Format("COUNT(distinct {0})", operands(0))
    End Function
    Public Shared Function CountDistinct(Of T)(ByVal collection As IEnumerable(Of T), ByVal arg As Expression(Of Func(Of T, Object))) As Object
        Throw New InvalidOperationException("This method should not be called explicitly.")
    End Function

    Public Function IsValidOperandCount(ByVal count As Integer) As Boolean Implements ICustomAggregateBrowsable.IsValidOperandCount
        Return True
    End Function

    Public Function IsValidOperandType(ByVal operandIndex As Integer, ByVal operandCount As Integer, ByVal type As Type) As Boolean Implements ICustomAggregateBrowsable.IsValidOperandType
        Return True
    End Function
End Class

Register the CountDistinct function at application startup:

csharp
CountDistinctCustomAggregate.Register();
vb
CountDistinctCustomAggregate.Register()

Run the application, open the Report Designer, and invoke the Expression Editor. You can use the CountDistinct function as shown in the following image:

View Example: Reporting for ASP.NET Core - How to Implement a Custom Function in the Expression Editor