locked
Understanding list of Func delegate (lambda expression) RRS feed

  • Question

  • Hi,

    I started using lambda expression and I use it often now but only the simple ones :-). Sometime I really get confused understanding lambda expressions in our existing code base. Tried hard to understand the code below but still not able to decipher completely :-(. I think because of the use of Func delegate I am not able to understand. I know that Func delegate is used when the delegate returns some thing. But in this case no clue.

    Code snippet:

    public class PrintProvider
        {
            private readonly IInstructionSheetViews _instructionSheetViews;
            public PrintProvider(IInstructionSheetViews instructionSheetViews)
            {
                _instructionSheetViews = instructionSheetViews;
            }
            public void AddReport()
            {
         // Some implementation code goes here
                var printViews = _instructionSheetViews.PrintViews;
         // Some implementation code goes here
            }
        }

        public class InstructionSheetViews : IInstructionSheetViews
        {
            private readonly IInstructionSheetFactory _factory;
            private IEnumerable<IReport> _instructionSheetView;
            private List<Func<IInstructionSheetFactory, IReport>> _instructionSheetViewList;

            public InstructionSheetViews(IInstructionSheetFactory factory)
            {
                _factory = factory;
            }

            public IEnumerable<IReport> PrintViews
            {
                get
                {
                    if (_instructionSheetView == null)
                    {
                        Init();
                        _instructionSheetView = _instructionSheetViewList.Select(x => x(_factory));
                    }

                    return _instructionSheetView;
                }
            }

            private void Init()
            {
                 _instructionSheetViewList = new List<Func<IInstructionSheetFactory, IReport>>();
                _instructionSheetViewList.Add(x => x.BuildCommonData());
                _instructionSheetViewList.Add(x => x.BuildSpecificData());
            }
        }

    In the above code snippet, AddReport method calls "_instructionSheetViews.PrintViews" and this method inturn calls "Init()".

    Q1. What is exactly getting added to "_instructionSheetViewList" here -
     _instructionSheetViewList.Add(x => x.BuildCommonData());.

    What I can guess is, it adds a method that returns a "IReport". But "_instructionSheetViewList" contains a list of "Func<IInstructionSheetFactory, IReport>". So, Ideally isn't it that it should contain a method that takes input as "IInstructionSheetFactory" and return "IReport"?

    Q2. How does this statement works. Basically the control flow.

     _instructionSheetViewList.Select(x => x(_factory));

    Can someone please explain me?

    Thanks in advance.

    Monday, October 10, 2016 3:34 PM

Answers

  • Q1. What is exactly getting added to "_instructionSheetViewList" here -
     _instructionSheetViewList.Add(x => x.BuildCommonData());.

    What I can guess is, it adds a method that returns a "IReport". But "_instructionSheetViewList" contains a list of "Func<IInstructionSheetFactory, IReport>". So, Ideally isn't it that it should contain a method that takes input as "IInstructionSheetFactory" and return "IReport"?

    Yes, in the lambda expression x => x.BuildCommonData(), x will be an IInstructionSheetFactory, and BuildCommonData should return an IReport or something that can be implicitly converted to IReport. BuildCommonData may be a method in the IInstructionSheetFactory interface or an extension method.

    Q2. How does this statement works. Basically the control flow.

     _instructionSheetViewList.Select(x => x(_factory));

    At compile time: Because the type of each element of _instructionSheetViewList is Func<IInstructionSheetFactory, IReport>, the type of x must likewise be Func<IInstructionSheetFactory, IReport>. Then, the x(_factory) expression is an invocation of a delegate, and the type of the result of x(_factory) is IReport. The Enumerable.Select method will thus return IEnumerable<IReport>.

    At run time: The code constructs a delegate of type Func<Func<IInstructionSheetFactory, IReport>, IReport> from the lambda expression x => x(_factory) and passes that to Enumerable.Select. However, because Enumerable.Select uses deferred evaluation, it does not invoke the delegate yet. It constructs an object that implements IEnumerable<IReport> and has references to the delegate and to the list that was read from _instructionSheetViewList.

    Later, if AddReport starts enumerating printViews, it calls printViews.MoveNext(), which reads the first element of _instructionSheetViewList and invokes the delegate that was constructed from x => x(_factory). The first element of _instructionSheetViewList is the delegate of type Func<IInstructionSheetFactory, IReport> that the Init method constructed from the lambda expression x => x.BuildCommonData(). That delegate becomes the value of x in the x(_factory) expression, which then invokes the delegate and passes _factory to it. In x.BuildCommonData(), x refers to the IInstructionSheetFactory that was read from _factory. The result of x.BuildCommonData() is an IReport, and that then becomes the result of x(_factory) as well. printViews.MoveNext() receives this result and saves it as printViews.Current.

    In the second printViews.MoveNext() call, the same thing happens, except now it reads the second element of _instructionSheetViewList and thus calls x.BuildSpecificData rather than x.BuildCommonData.

    The code would be easier to explain clearly if it didn't use the same name "x" in both "x => x(_factory)" and "x => x.BuildCommonData()". Perhaps those should be "makeView => makeView(_factory)" and "factory => factory.BuildCommonData()". However, it isn't clear to me whether the author intended a "view" to mean a set of reports, a single report, or a mapping from instruction sheet factories to reports.

    If the InstructionSheetViews implementation that you posted is complete, then I think it could be simplified quite a lot:

    public class InstructionSheetViews : IInstructionSheetViews
    {
        private readonly IInstructionSheetFactory _factory;
    
        public IInstructionSheetViews(IInstructionSheetFactory factory)
        {
            _factory = factory;
        }
    
        public IEnumerable<IReport> PrintViews
        {
            get
            {
                yield return _factory.BuildCommonData();
                yield return _factory.BuildSpecificData();
            }
        }
    }
    i.e. replace the delegates and lambda expressions with an iterator. This retains the deferred evaluation.
    Friday, October 14, 2016 9:05 PM