C# 如何按文件夹对文件(字符串)列表进行分组,并对组执行select方法

C# 如何按文件夹对文件(字符串)列表进行分组,并对组执行select方法,c#,linq,lambda,C#,Linq,Lambda,我正在为我们项目中的图像创建一个常量cs文件 所以我有一个这样的文件列表: var SourceFiles = new List<String>() { "Images/BankLogos/ic_card_amex.svg", "Images/BankLogos/ic_nocards.svg", "Images/Cars/Toyota_Auris_TS_Estate.png", "

我正在为我们项目中的图像创建一个常量cs文件

所以我有一个这样的文件列表:

var SourceFiles = new List<String>() {
    "Images/BankLogos/ic_card_amex.svg", 
    "Images/BankLogos/ic_nocards.svg",
    "Images/Cars/Toyota_Auris_TS_Estate.png",
    "Images/Icons/ic_current_location_circle.svg",
    "Images/Icons/ic_abn_partially_available_circle.svg",
    "Images/ic_menu.svg",
};
我有一些代码来写外部部分

        private string GenerateCode(IEnumerable<string> files)
        {
            var content = string.Join(
                $"{Environment.NewLine}\t",
                files.Select(GenerateProperty));

            var code = $@"
// Generated code, do not edit.
namespace Common 
{{
    public static class Constants
    {{
        {content}
    }}
}}";
            return code;
        }

        private static string GenerateProperty(string file)
        {
            var ext = GetExt(file);
            var withoutExt = RemoveExt(ext, file);
            var name = LetterOrDigit(withoutExt);

            var v = file.Replace("\\", "\\\\");

            return string.Format(
                "public static readonly string {0} = \"{1}\";",
                name,
                v
            );
        }
private string GenerateCode(IEnumerable文件)
{
var content=string.Join(
$“{Environment.NewLine}\t”,
选择(GenerateProperty));
变量代码=$@”
//生成的代码,不要编辑。
名称空间公用
{{
公共静态类常量
{{
{content}
}}
}}";
返回码;
}
私有静态字符串生成器属性(字符串文件)
{
var ext=GetExt(文件);
var withoutExt=RemoveExt(ext,file);
变量名称=LetterOrdGit(无文本);
var v=file.Replace(“\\”,“\\\”);
返回字符串格式(
“公共静态只读字符串{0}=\”{1}\”;“,
名称
v
);
}
因此,我想如果我可以将列表分组,然后在
GenerateProperty
中选择并传递列表,我可以像
GenerateCode
那样写出每个部分


因此,我认为我可以管理大部分内容,但我不确定谁应该按文件夹对文件/字符串进行分组,然后在该列表上执行
选择

我使用了很多扩展方法,因此我使用现有的扩展库构建了我的答案

首先,扩展:

public static class StringExt {
    // return new string consisting only of letter or digit characters from s
    public static string LetterOrDigit(this string s) => s.Where(c => Char.IsLetterOrDigit(c)).Join();

    // build new string from n copies of s
    public static string Repeat(this string s, int n) => new StringBuilder(s.Length * n).Insert(0, s, n).ToString();

    // return s upto (stopping before) the first occurrence of stopRE
    // or all of s if stopRE is not present
    public static string UpTo(this string s, Regex stopRE) {
        var m = stopRE.Match(s);
        return m.Success ? s.Substring(0, m.Index) : s;
    }

    // return s past the first occurrence of stopRE
    // or all of s if stopRE is not present
    public static string Past(this string s, Regex startRE) {
        var m = startRE.Match(s);
        return m.Success ? s.Substring(m.Index + m.Length) : s;
    }

    // return true if re is present in (matches) s
    public static bool IsMatch(this string s, Regex re) => re.IsMatch(s);
}

public static class IEnumerableExt {
    // return a new string by joining the chars together
    public static string Join(this IEnumerable<char> chars) => String.Concat(chars); // faster >= .Net Core 2.1
    // return a new string by combining the elements of s, separated by sep
    public static string Join(this IEnumerable<string> s, string sep) => String.Join(sep, s);
}
接下来是一个新的helper方法,它递归地从路径生成类。代码有点复杂,因为我使用了LINQ,所以我可以使用
Join
方法在节之间插入换行符,这样就不会有额外的空行。通常,您可以读取
enumerable。选择(v=>
作为类似于
foreach(enumerable中的var v)


那么,你的问题是什么?@PeterCsala我已经编辑了这个问题…谢谢..你的
Cars
成员似乎有错误的名称。请尝试关键字
T4
,它可以在你保存问题时自动执行file@MichaelMao我正在使用VS for mac和EnvDTE ism,但还没有完全可用。哇,谢谢,这非常有效,我会接受的……不过,我不懂
GenerateProperties
,我以为我懂Linq,但它比我习惯的要复杂得多。请你能在大多数行上发表一些评论吗?正如我所说的,不管怎样,我都乐意接受。此外,我还将选项卡更改为\t,这会把事情搞砸…:(@Jules I将添加一些注释。您应该能够用
“\t”
替换四个空格,它应该可以很好地工作-我一开始就这样做了。@Jules I添加了注释(可能有帮助)并使用了
“\t”
,并将开始的
深度修改为
1
,以便更好地处理选项卡。谢谢
public static class StringExt {
    // return new string consisting only of letter or digit characters from s
    public static string LetterOrDigit(this string s) => s.Where(c => Char.IsLetterOrDigit(c)).Join();

    // build new string from n copies of s
    public static string Repeat(this string s, int n) => new StringBuilder(s.Length * n).Insert(0, s, n).ToString();

    // return s upto (stopping before) the first occurrence of stopRE
    // or all of s if stopRE is not present
    public static string UpTo(this string s, Regex stopRE) {
        var m = stopRE.Match(s);
        return m.Success ? s.Substring(0, m.Index) : s;
    }

    // return s past the first occurrence of stopRE
    // or all of s if stopRE is not present
    public static string Past(this string s, Regex startRE) {
        var m = startRE.Match(s);
        return m.Success ? s.Substring(m.Index + m.Length) : s;
    }

    // return true if re is present in (matches) s
    public static bool IsMatch(this string s, Regex re) => re.IsMatch(s);
}

public static class IEnumerableExt {
    // return a new string by joining the chars together
    public static string Join(this IEnumerable<char> chars) => String.Concat(chars); // faster >= .Net Core 2.1
    // return a new string by combining the elements of s, separated by sep
    public static string Join(this IEnumerable<string> s, string sep) => String.Join(sep, s);
}
private static string GenerateProperty(string file) {
    var ext = Path.GetExtension(file);
    var withoutExt = Path.GetFileNameWithoutExtension(file);
    var name = withoutExt.LetterOrDigit();

    var v = file.Replace("\\", "\\\\");
    return $"public static readonly string {name} = \"{v}\";";
}
static Regex sepRE = new Regex(@"[/\\]", RegexOptions.Compiled);
private static string GenerateProperties(IEnumerable<string> files, int nestDepth = 1) {
    return Generator(files.Select(f => (full: f, left: f)), nestDepth);

    // internal nested method to convert list of paths to class declarations and member property declarations
    // calls self recursively to handle sub-folders in paths
    string Generator(IEnumerable<(string full, string left)> files, int nestDepth) {
        string tabs = "\t".Repeat(nestDepth + 2);

                  // group tuples of (full path, rest of path after first folder) by first path folder
        var ans = files.GroupBy(n => n.left.UpTo(sepRE), n => (n.full, left: n.left.Past(sepRE)))
                  // foreach (var fg in files.GroupBy...) -- fg is group of paths in a folder
                        // build class declaration from folder
                       .Select(fg => $"{tabs}public static class {fg.Key.LetterOrDigit()}\n{tabs}{{\n" +
                                    // group (separate) sub-paths into paths in further folders and paths in root of this folder/class
                                     fg.GroupBy(n => n.left.IsMatch(sepRE))
                                        // foreach (var sfg in GroupBy...) -- sfg is group of subfolder paths or root paths
                                       .Select(sfg =>
                                            sfg.Key
                                                // if in subfolders, call recursively to create classes for subfolders
                                                ? Generator(sfg, nestDepth + 1) // more nested classes
                                                // if in root, just create properties for each root member path
                                                : sfg.Select(n => tabs+"    "+GenerateProperty(n.full)).Join("\n")
                                       )
                                       // join the sub-folder declarations and root property declarations
                                       // separated by newlines
                                       .Join("\n") +
                                    // end the class declaration
                                     $"\n{tabs}}}\n")
                        // join the class declarations at a level with newlines
                        // (creating a blank line between each class declaration)
                       .Join("\n");

        return ans;
    }
}
private string GenerateCode(IEnumerable<string> files) {
    var content = string.Join(
        $"{Environment.NewLine}\t",
        GenerateProperties(files));

    var code = $@"
// Generated code, do not edit.
namespace Common
{{
    public static class Constants
    {{
{content}    }}
}}";
    return code;
}