C# – Can you refactor out a common functionality from these two methods

.net, c++, csv, refactoring

I have two methods that basically converts underlying checkboxes' text or tag as CSV strings.

These two methods

  • GetSelectedTextAsCsv()
  • GetTagAsCsv()

differ only by which property to extract value from SelectedCheckBoxes, which is of type IList<CheckBox>

    public string GetSelectedTextAsCsv()    {        var buffer = new StringBuilder();        foreach (var cb in SelectedCheckBoxes)        {            buffer.Append(cb.Text).Append(",");        }        return DropLastComma(buffer.ToString());    }    public string GetTagAsCsv()    {        var buffer = new StringBuilder();        foreach (var cb in SelectedCheckBoxes)        {            buffer.Append(cb.Tag).Append(",");        }        return DropLastComma(buffer.ToString());    }

I was trying to extract a method that returns a Func<T, TResult> but not sure how I can pull that off.
My poor attempt was like the following but I cannot figure out how to extract the property portion as shown in the comment within ConvertToCsv()

    public Func<T, string> ConvertToCsv<T>()    {        return propertyName =>        {            var buffer = new StringBuilder();            foreach (var checkBox in SelectedCheckBoxes)            {                buffer.Append(                    /* How can you abstract this portion? like following? */                     checkBox.propertyName                ).Append(",");            }            return DropLastComma(buffer.ToString());        };    }

If I am on a wrong track, would you please advise me on how I can refactor above code to use a common method?

[UPDATE 1] Here is the combination of both Brian and Jon's answers

    public string ConvertToCsv<T>(Func<CheckBox, T> getValue)    {        var stringValues = SelectedCheckBoxes.Select(            cb => getValue(cb).ToString()).ToArray();        return string.Join(",", stringValues);    }    public string GetSelectedTextAsCsv()    {        return ConvertToCsv(cb => cb.Text);    }    public string GetTagAsCsv()    {        return ConvertToCsv(cb => cb.Tag);    }

[UPDATE 2] version 2

    public string GetAsCsv<T>(Func<CheckBox, T> getValue)    {        return string.Join(",", SelectedCheckBoxes.Select(            cb => getValue(cb).ToString()).ToArray());    }    public string GetSelectedTextAsCsv()    {        return GetAsCsv(cb => cb.Text);    }    public string GetTagAsCsv()    {        return GetAsCsv(cb =>             cb.Tag == null ? string.Empty : cb.Tag.ToString());    }

[UPDATE 3] Made the parameter of GetAsCsv() as a closed generic of CheckBox and string

Func<CheckBox, T> to Func<CheckBox, string>.

That allowed me to make GetAsCsv() even simpler and more readable.

private string GetAsCsv(Func<CheckBox, string> getValue){    return string.Join(",", SelectedCheckBoxes.Select(getValue).ToArray());}

Best Solution

public string GetAsCsv(Func<CheckBox, string> getValue){    var buffer = new StringBuilder();    foreach (var cb in SelectedCheckBoxes)    {        buffer.Append(getValue(cb)).Append(",");    }    return DropLastComma(buffer.ToString());}

Then:

GetAsCsv(cb => cb.Tag != null ? cb.Tag.ToString() : string.Empty);GetAsCsv(cb => cb.Text);