【问题标题】:How to call a method that needs a Func<T> with an lambda Expression argument如何使用 lambda 表达式参数调用需要 Func<T> 的方法
【发布时间】:2019-02-09 05:26:24
【问题描述】:

我知道我可以调用一个直接采用 lambda 表达式的泛型方法。
例如,在下面的代码中,我可以直接调用此方法,但这不会达到我的需要: GetAllItemsFromTableAsync(x => x.ID =3)

我想要传递一个泛型方法作为委托,我想传递该方法(或它的派生方法)一个 lambda 表达式参数(实际上是一个数据库过滤器),最终将调用 GetAllItemsFromTableAsync 方法,提供必要的过滤器表达式。

是否可以将方法作为委托传递,并在该方法接受参数时为该方法提供 lambda 表达式?在我的代码中,您将看到两个示例区域,一个当前可以工作(虽然它与原始代码相比非常简化,但我没有编译它但认为它传达了基本思想)另一个标记为“不工作”是我想要的如果可能的话,喜欢以某种方式实现。目前不是因为我不知道如何引用传递的参数以便将其菊花链到另一个方法。这个可以吗?

我对 lambda 有点陌生,说实话,我仍在努力解决这一切。任何帮助将不胜感激。

           #region This model does work currently (overly simplified for example purposes)
    /// <summary>
    /// Loads the various controls on the page that need special processing
    /// </summary>
    /// <returns></returns>
    protected async Task RefreshControls()
    {
        await LoadPickerAsync<Car, ViewModel<Car>, List<Car>>(
            this.GetPropertyInfo(x => x.AvailableColors),
            this.GetPropertyInfo(y => y.ColorIndex),
            this.GetPropertyInfo(z => z.Color),
            LoadFromDBAsync<Colors, List<Colors>>);

        await LoadPickerAsync<Truck, ViewModel<Truck>, List<Truck>>(
            this.GetPropertyInfo(x => x.AvailableColors),
            this.GetPropertyInfo(y => y.ColorIndex),
            this.GetPropertyInfo(z => z.Color),
            LoadFromDBAsync<Colors, List<Colors>>);
    }

    /// <summary>
    /// This method is first stop in the load process.  It sorts out the properties and then calls the 
    /// delegate method (in this example LoadFromDBAsync) to perform the actual loading of the picker list.
    /// </summary>
    /// <typeparam name="TColItemType"></typeparam>
    /// <typeparam name="TViewModel"></typeparam>
    /// <typeparam name="TCollection"></typeparam>
    /// <param name="collectionProperty"></param>
    /// <param name="indexProperty"></param>
    /// <param name="objectProperty"></param>
    /// <param name="collectionLoaderAsync"></param>
    /// <returns></returns>
    protected virtual async Task<TCollection> LoadPickerAsync<TColItemType, TViewModel, TCollection>(
            PropertyInfo collectionProperty,
            PropertyInfo indexProperty,
            PropertyInfo objectProperty,
            Func<Task<TCollection>> collectionLoaderAsync) 
        //Note I need to know how to pass a filter to the method here such as "x => x.ID = 2"
                where TCollection : List<TColItemType>
    {
        //Do some special stuff with the properties

        //Actually Load the picker
        if (await collectionLoaderAsync() != default(TCollection))
        {
            //do something useful, set a flag etc...
        }
    }


    /// <summary>
    /// This method would be within each view model and may be totally different from one view
    /// model to the next and even some controls may use their own method as long as the 
    /// basic signature remains the same
    /// </summary>
    /// <typeparam name="TColItemType"></typeparam>
    /// <typeparam name="TCollection"></typeparam>
    /// <returns></returns>
    protected virtual async Task<TCollection> LoadFromDBAsync<TColItemType, TCollection>()
        //I'd like to add a way for this method to pass a filter: "Expression<Func<TColItemType, bool>> filter = null"
            where TCollection : List<TColItemType>, new()
            where TColItemType : new()
    {
        //if I add the argument parameter to the method I can not figure out how to make the call 
        //to this method in LoadPickerAsync method above
        //Expression<Func<T, bool>> filter = null,

        //do some more stuff with the loading of the items into the picker

        //Call the method to load the data and return
        //note that "fitler" is only applicable if I can add the parameter above
        return (TCollection)await GetAllItemsFromTableAsync<TColItemType>(); 
    }


    /// <summary>
    /// Gets all data from the database table with the specified filter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="filter"></param>
    /// <param name="cancelToken"></param>
    /// <returns></returns>
    protected async Task<List<TColItemType>> GetAllItemsFromTableAsync<TColItemType>() where TColItemType : new()
    {
        //Do some prep work and null checks before calling the actual database method
        //

        //Call the database method to obtain the data
        return await database.GetAllWithChildrenAsync<TColItemType>(recursive: true);
    }
    #endregion This model does work currently (overly simplified for example purposes)


    #region Does NOT work
    /// <summary>
    /// Loads the various controls on the page that need special processing
    /// </summary>
    /// <returns></returns>
    protected async Task RefreshControls()
    {
        await LoadPickerAsync<Car, ViewModel<Car>, List<Car>>(
            this.GetPropertyInfo(x => x.AvailableColors),
            this.GetPropertyInfo(y => y.ColorIndex),
            this.GetPropertyInfo(z => z.Color),
            LoadFromDBAsync<Colors, List<Colors>>(x => x.ID == 4));

        await LoadPickerAsync<Truck, ViewModel<Truck>, List<Truck>>(
            this.GetPropertyInfo(x => x.AvailableColors),
            this.GetPropertyInfo(y => y.ColorIndex),
            this.GetPropertyInfo(z => z.Color),
            LoadFromDBAsync<Colors, List<Colors>>(x => x.ID == 3));
    }

    /// <summary>
    /// This method is first stop in the load process.  It sorts out the properties and then calls the 
    /// delegate method (in this example LoadFromDBAsync) to perform the actual loading of the picker list.
    /// </summary>
    /// <typeparam name="TColItemType"></typeparam>
    /// <typeparam name="TViewModel"></typeparam>
    /// <typeparam name="TCollection"></typeparam>
    /// <param name="collectionProperty"></param>
    /// <param name="indexProperty"></param>
    /// <param name="objectProperty"></param>
    /// <param name="collectionLoaderAsync"></param>
    /// <returns></returns>
    protected virtual async Task<TCollection> LoadPickerAsync<TColItemType, TViewModel, TCollection>(
            PropertyInfo collectionProperty,
            PropertyInfo indexProperty,
            PropertyInfo objectProperty,
            Func<Expression<Func<TColItemType, bool>>, Task<TCollection>> collectionLoaderAsync) 
            //Note I need to know how to pass a filter argument to the method here such as "x => x.ID = 2"
                where TCollection : List<TColItemType>
    {
        //Do some special stuff with the properties

        //With this overload example I can not figure out how to reference the argument passed in inorder to 
        //subsequently pass that argument when making the call to the method

        //Actually Load the picker
        await collectionLoaderAsync(filterArg);
    }


    /// <summary>
    /// This method would be within each view model and may be totally different from one view
    /// model to the next and even some controls may use their own method as long as the 
    /// basic signature remains the same
    /// </summary>
    /// <typeparam name="TColItemType"></typeparam>
    /// <typeparam name="TCollection"></typeparam>
    /// <returns></returns>
    protected virtual async Task<TCollection> LoadFromDBAsync<TColItemType, TCollection>(
        Expression<Func<TColItemType, bool>> filter = null)
        //I'd like to add a way for this method to pass a filter: "Expression<Func<TColItemType, bool>> filter = null"
            where TCollection : List<TColItemType>, new()
            where TColItemType : new()
    {
        //if I add the argument parameter to the method I can not figure out 
        //how to make the call to this method in LoadPickerAsync method above
        //Expression<Func<T, bool>> filter = null,

        //do some more stuff with the loading of the items into the picker

        //Call the method to load the data and return
        //note that "fitler" is only applicable if I can add the parameter above
        return GetAllItemsFromTableAsync<TColItemType>(filter); 
    }


    /// <summary>
    /// Gets all data from the database table with the specified filter
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="filter"></param>
    /// <param name="cancelToken"></param>
    /// <returns></returns>
    protected async Task<List<TColItemType>> GetAllItemsFromTableAsync<TColItemType>(
            Expression<Func<TColItemType, bool>> filter = null) where TColItemType : Car, new()
    {
        //Do some prep work and null checks before calling the actual database method
        //

        //Call the database method to obtain the data
        return await database.GetAllWithChildrenAsync<TColItemType>(recursive: true, filter: filter);
    }


    #endregion Does NOT work

【问题讨论】:

  • 您确实需要将这段代码简化大约 99%。否则人们不会花时间阅读它。

标签: c# lambda delegates expression func


【解决方案1】:

如果您编写 LoadFromDBAsync&lt;Colors, List&lt;Colors&gt;&gt;(x =&gt; x.ID == 4),这将调用您的 LoadFromDBAsync 方法。如果您想将其作为委托传递,则需要传递一个 lambda 表达式:

await LoadPickerAsync<Car, ViewModel<Car>, List<Car>>(
  this.GetPropertyInfo(x => x.AvailableColors),
  this.GetPropertyInfo(y => y.ColorIndex),
  this.GetPropertyInfo(z => z.Color),
  () => LoadFromDBAsync<Colors, List<Colors>>(x => x.ID == 4));

背景:您的第一个示例中的速记符号,将“方法组”(即LoadFromDBAsync&lt;Colors, List&lt;Colors&gt;&gt; 传递给collectionLoaderAsync 参数,仅当方法在参数处具有相同签名时才有效类型(即Func&lt;Task&lt;TCollection&gt;&gt;)。在第二个示例中,由于额外的filter 参数,签名不匹配。因此,需要上述完整的lambda 表示法。

【讨论】:

  • 非常感谢。这么简单的解决方案。我想我以某种方式走在正确的道路上。您的回答完美无缺。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多