Is it possible to explode an array so that its elements can be passed to a method with the params keyword?
Asked Answered
I

5

6

Take this non-compiling code for instance:

public string GetPath(string basefolder, string[] extraFolders)
{
    string version = Versioner.GetBuildAndDotNetVersions();
    string callingModule = StackCrawler.GetCallingModuleName();
    return AppendFolders(basefolder, version, callingModule, extraFolders);
}
private string AppendFolders(params string[] folders)
{
    string outstring = folders[0];
    for (int i = 1; i < folders.Length; i++)
    {
        string fixedPath = folders[i][0] == '\\' ? folders[i].Substring(1) : folders[i];
        Path.Combine(outstring, fixedPath);
    }
    return outstring;
}

This example is a somewhat simplified version of testing code I am using. Please, I am only interested in solutions having directly to do with the param keyword. I know how lists and other similar things work.

Is there a way to "explode" the extraFolders array so that it's contents can be passed into AppendFolders along with other parameters?

Intaglio answered 10/10, 2008 at 19:19 Comment(3)
Just to be sure I understand, you want the extraFolders array to be sent into the AppendFolders method as AppendFolders(extraFolders[0], extraFolders[1], ...etc etc) ? Interesting question, I'd be curious if that's possible, although I don't see how....Goldenseal
Your clarification just adds more confusion. Are the extra items being passed an important part of this or not? If not, the first half of my answer is what you want. If they are, this is a very special case, handled by the second half of my answer.Septicidal
Both are important but the context they are used is isn't. What I am trying to say is I don't care that they are paths or I am using Path.Combine, I just want an easy one-liner to bring both sets of parameters together so they can be passed into a params method.Intaglio
I
1

One option is to make the params parameter an object[]:

static string appendFolders(params object[] folders)
 { return (string) folders.Aggregate("",(output, f) => 
                       Path.Combine( (string)output
                                    ,(f is string[]) 
                                      ? appendFolders((object[])f)
                                      : ((string)f).TrimStart('\\')));
 }

If you want something more strongly-typed, another option is to create a custom union type with implicit conversion operators:

  static string appendFolders(params StringOrArray[] folders)
     { return folders.SelectMany(x=>x.AsEnumerable())
                     .Aggregate("",
                       (output, f)=>Path.Combine(output,f.TrimStart('\\')));
     }

   class StringOrArray
     { string[] array;

       public IEnumerable<string> AsEnumerable()
        { return soa.array;}

       public static implicit operator StringOrArray(string   s)   
        { return new StringOrArray{array=new[]{s}};}

       public static implicit operator StringOrArray(string[] s)  
        { return new StringOrArray{array=s};}
     }

In either case, this will compile:

appendFolders("base", "v1", "module", new[]{"debug","bin"});
Ilyssa answered 10/10, 2008 at 21:14 Comment(0)
S
8

Just pass it. The folders parameter is an array first. the "params" functionality is a little bit of compiler magic, but it's not required.

AppendFolders(extraFolders);

Now, it this particulat instance, you'll have to add some things to that array, first.

List<string> lstFolders = new List<string>(extraFolders);
lstFolder.Insert(0, callingModule);
lstFolder.Insert(0, version);
lstFolder.Insert(0, basefolder);
return AppendFolders(lstFolders.ToArray());
Septicidal answered 10/10, 2008 at 19:33 Comment(1)
I guess the point was that you can't just add things to an array :)Paediatrician
T
2

I'll quibble with the term "collapse", since it seems you really want to "expand". And I'm not sure what you mean by solutions "having directly to do with params keyword" and that "you're not interested in workarounds". In the end, you either have to pass a number of strings - which the compiler will magically package into an array - or an array of strings directly. That being said, my solution (without changing the interface) would go something like:

return AppendFolders(new string[] { basefolder, version, callingModule }.Concat(extraFolders).ToArray());

Edit:

While you can't add an operator via extension methods, you could do:

return AppendFolders(new string[] { baseFolder, callingModuleName, version }.Concat(extraFolders));

public static T[] Concat<T>(this T[] a, T[] b) {
   return ((IEnumerable<T>)a).Concat(b).ToArray();
}

But, if we're going to go that far - might as well just extend List<T> to handle this elegantly:

return AppendFolders(new Params<string>() { baseFolder, callingModuleName, version, extraFolders });

class Params<T> : List<T> {
    public void Add(IEnumerable<T> collection) {
       base.AddRange(collection);
    }

    public static implicit operator T[](Params<T> a) {
       return a.ToArray();
    }
}
Trabue answered 10/10, 2008 at 19:51 Comment(2)
This is really close to what I want. Elegant and similar to what the a compiler might do.Intaglio
Equally cool would be: new string[] { baseFolder, callingModuleName, version } + extraFoldersIntaglio
P
1

A quick and dirty solution would be to build a List<string> from the items and then pass that (with ToArray()).

Note that you don't need to test for the backslash. Path.Combine handles the dirty things rather fine.

Paediatrician answered 10/10, 2008 at 19:23 Comment(3)
In some cases it won't handle the backslash properly. I have ended up with a double backslash in the past. ex. Blah\\BlahIntaglio
Then it might be a bug which you can file on connect.microsoft.com - and if you want to stay with doing the backslash thing, at least use Path.DirectorySeparatorChar instead of a hard-coded backslash.Paediatrician
Path.Combine has problems if second path begins with a backslash.Spital
H
1

I think OregonGhost's answer is probably the way you want to go. Just to elaborate on it, he's suggesting doing something like this:

public string GetPath(string basefolder, string[] extraFolders)
{
    string version = Versioner.GetBuildAndDotNetVersions();
    string callingModule = StackCrawler.GetCallingModuleName();

    List<string> parameters = new List<string>(extraFolders.Length + 3);
    parameters.Add(basefolder);
    parameters.Add(version);
    parameters.Add(callingModule);
    parameters.AddRange(extraFolders);
    return AppendFolders(parameters.ToArray());
}

And I don't mean that as a lesson on how to use Lists, just as a little clarification for anybody who may come along looking for the solution in the future.

Hautemarne answered 10/10, 2008 at 19:45 Comment(0)
I
1

One option is to make the params parameter an object[]:

static string appendFolders(params object[] folders)
 { return (string) folders.Aggregate("",(output, f) => 
                       Path.Combine( (string)output
                                    ,(f is string[]) 
                                      ? appendFolders((object[])f)
                                      : ((string)f).TrimStart('\\')));
 }

If you want something more strongly-typed, another option is to create a custom union type with implicit conversion operators:

  static string appendFolders(params StringOrArray[] folders)
     { return folders.SelectMany(x=>x.AsEnumerable())
                     .Aggregate("",
                       (output, f)=>Path.Combine(output,f.TrimStart('\\')));
     }

   class StringOrArray
     { string[] array;

       public IEnumerable<string> AsEnumerable()
        { return soa.array;}

       public static implicit operator StringOrArray(string   s)   
        { return new StringOrArray{array=new[]{s}};}

       public static implicit operator StringOrArray(string[] s)  
        { return new StringOrArray{array=s};}
     }

In either case, this will compile:

appendFolders("base", "v1", "module", new[]{"debug","bin"});
Ilyssa answered 10/10, 2008 at 21:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.