C# passing multiple properties as parameters for fluid logging

I am trying to pass multiple properties to a method for seamless logging. I am iterating over a list of files and hope to log them based on the properties' values. Currently, I am using enum and bool properties, but I can imagine needing to use other parameters in the future.

public enum MyTypes {
    Type1,
    Type2,
    Type3
}

public class MyFile {
    public string Filename { get; set; }
    public bool ClassBool { get; set; }
    public MyTypes Type { get; set; }
}

Without this optimization, the method would have to contain loops for each comparison:

public void LogFiles(List<MyFile> files) {
    
    logger.Info("--- Begin Type1 Print ---\n\n");
    for (int i = 0; i < files.Count; i++) {
         if (files[i].Type == MyTypes.Type1)
             logger.Info(files[i].Filename);
    }
    logger.Info("--- End Type1 Print ---\n\n");

    logger.Info("--- Begin Type2 Print ---\n\n");
    for (int i = 0; i < files.Count; i++) {
         if (files[i].Type == MyTypes.Type2)
             logger.Info(files[i].Filename);
    }
    logger.Info("--- End Type2 Print ---\n\n");

    logger.Info("--- Begin Class Bool Print ---\n\n");
    for (int i = 0; i < files.Count; i++) { //Example of not passing the same param
         if (ClassBool == true)
             logger.Info(files[i].Filename);
    }
    logger.Info("--- End Class Bool Print ---\n\n");
}

I would like to pass in the files, a logging message (that may not be the same as the name of the property), and the property. I imagine I will also have to pass in a delegate of some sort, but I don't know how to do that yet. I imagine something similar to this (not sure how to exactly code this, so it's just instructive pseudocode):


public void LogFiles(List<MyFile> files, string message, PropertyInfo prop, Delegate<UnsureHere> del)
    logger.Info($"--- Begin {message} Print ---\n\n");
    for (int i = 0; i < files.Count; i++) {
        if (/*may use delegate here*/)
            logger.Info(/*access property*/);
    }
    logger.Info($"--- End {message} Print ---\n\n");
}

All of this to achieve a use case like this:

List<MyFile> files = new();
LogFiles(files, "Type1", /*not sure how to pass these*/);
LogFiles(files, "Type2", /*not sure how to pass these*/);
LogFiles(files, "Class Bool", /*not sure how to pass these*/);

I understand that something similar could be achieved with a single loop and a switch statement, but I like the format that the code above would produce. Is this possible? Thank you for the help!


Solution 1:

You can add some dynamic function parameters to achieve your goal. You need two dynamic functions:

  • one to return a bool based on the contents of a MyFile which will control whether the information is logged Func<MyFile, bool> includeSelector

  • another one to return the string data to log Func<MyFile, string> dataSelector

You could implement the above like so:

public void LogFiles(
    List<MyFile> files,
    string message,
    Func<MyFile, bool> includeSelector,
    Func<MyFile, string> dataSelector
)
{
    logger.Info($"--- Begin {message} Print ---\n\n");
    for (int i = 0; i < files.Count; i++)
    {
        if (includeSelector(files[i]))
            logger.Info(dataSelector(files[i]));
    }
    logger.Info($"--- End {message} Print ---\n\n");
}

Using LINQ, you could shorten it to:

public void LogFiles(
    List<MyFile> files,
    string message,
    Func<MyFile, bool> includeSelector,
    Func<MyFile, string> dataSelector
)
{
    logger.Info($"--- Begin {message} Print ---\n\n");
    foreach (var file in files.Where(f => includeSelector(f)))
        logger.Info(dataSelector(f);
    logger.Info($"--- End {message} Print ---\n\n");
}

You would call this using

LogFiles(files, "Type1", x => x.ClassBool, x => x.Filename);

x => x.ClassBool is a function that will take a MyFile argument on the left side of the arrow and return the result of the operation on the right side. Note that there is no actual variable x declared in your code; I just chose to use that as the variable name for that dynamic function.

This is the same idea as a LINQ expression.