C
C#ā€¢3mo ago
Jason_Bjorn

āœ… Generic function with conditional type based logic inside

How can I make something like this work? I want the user to either be able to get a list of ints or strings.
List<int> lInt = Foo<int>();
List<string> lString = Foo<string>();

// Should either returns a List of Ints or strings
List<T> Foo<T>()
{
List<T> list = new();

for (int i = 0; i < 10; i++)
{
T valueToAdd;
if (typeof(T) == typeof(int))
{
valueToAdd = i;
}
else if (typeof(T) == typeof(string))
{
valueToAdd = i.ToString();
}
else {
throw new InvalidOperationException($"Invalid type {typeof(T)}");
}
list.Add(valueToAdd);
}
return list;
}
List<int> lInt = Foo<int>();
List<string> lString = Foo<string>();

// Should either returns a List of Ints or strings
List<T> Foo<T>()
{
List<T> list = new();

for (int i = 0; i < 10; i++)
{
T valueToAdd;
if (typeof(T) == typeof(int))
{
valueToAdd = i;
}
else if (typeof(T) == typeof(string))
{
valueToAdd = i.ToString();
}
else {
throw new InvalidOperationException($"Invalid type {typeof(T)}");
}
list.Add(valueToAdd);
}
return list;
}
17 Replies
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
I want to avoid boxing as well And I don't want to duplicate the logic I don't know why it thinks it needs to implicitly convert. The types will match up because I checked them before
Joschi
Joschiā€¢3mo ago
First of all a generic method should work for all types it accepts. If a method cannot act on all possible values for T (on a conceptual level), you need to restrict the generic down to a point where that is possible. But what are you exactly trying to accomplish with this method, what is your goal?
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
I want to avoid having to write two functions that are 90% identical @Joschi yes, I agree. this feels wrong.
Joschi
Joschiā€¢3mo ago
What you could do to make this work is to force the user to provide a function that can convert your int i to T. So your signature could be something like List<T> Foo<T>(Func<int, T> func). Now you could use IEnumerable<T> instead of list and convert this to a generator with yield return. Then you may realise that you reinvented Enumerable.Range(0, 10).Select(x => x.ToString()).
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
I like your first two points, I will try to do something like that. For the third, this example is just something small that I could post on discord without including a bunch of extra crap.
Joschi
Joschiā€¢3mo ago
I suspected as much. But I always think it's a bit funny, that code often suddenly evolves into LINQ, as soon as loops and returning Enumerables of any kind are involved.
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
I will post my actual version when I get it done
Joschi
Joschiā€¢3mo ago
Feel free to ping me
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
will do, thanks
public static HashSet<T> GetFileHashesDirectory<T>(string directory, Func<FileInfo, T> fileToHash)
{
int numFiles = GetNumFilesInDirectory(directory);

HashSet<T> AllHashes = new(numFiles);

foreach (string file in Directory.EnumerateFiles(directory))
{
FileInfo fInfo = new(file);
T fileHash = fileToHash(fInfo);
AllHashes.Add(fileHash);
}
return AllHashes;
}
public static HashSet<T> GetFileHashesDirectory<T>(string directory, Func<FileInfo, T> fileToHash)
{
int numFiles = GetNumFilesInDirectory(directory);

HashSet<T> AllHashes = new(numFiles);

foreach (string file in Directory.EnumerateFiles(directory))
{
FileInfo fInfo = new(file);
T fileHash = fileToHash(fInfo);
AllHashes.Add(fileHash);
}
return AllHashes;
}
@Joschi
Joschi
Joschiā€¢3mo ago
Looks good. Would you give me an example of what fileToHash would do?
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
HashSet<string> allHashesString = FileUtils.GetFileHashesDirectory(dirA, FileUtils.GetFileHashString); Here is the call
using System.Security.Cryptography;

namespace DirectoryDiff;

public static class FileUtils
{
private static readonly Lazy<SHA256> Sha256 = new(SHA256.Create);

public static byte[] GetFileHashBytes(FileInfo fInfo, SHA256 sha256)
{
using FileStream fileStream = fInfo.Open(FileMode.Open);
byte[] hash = sha256.ComputeHash(fileStream);
return hash;
}

public static byte[] GetFileHashBytes(FileInfo fInfo)
{
return GetFileHashBytes(fInfo, Sha256.Value);
}

public static string GetFileHashString(FileInfo fInfo, SHA256 sha256)
{
return HashBytesToString(GetFileHashBytes(fInfo, sha256));
}

public static string GetFileHashString(FileInfo fInfo)
{
return GetFileHashString(fInfo, Sha256.Value);
}

public static string HashBytesToString(byte[] hash)
{
return BitConverter.ToString(hash).Replace("-", "");
}

private static int GetNumFilesInDirectory(string directory)
{
return Directory.EnumerateFiles(directory).Count();
}

public static HashSet<T> GetFileHashesDirectory<T>(string directory, Func<FileInfo, T> fileToHash)
{
int numFiles = GetNumFilesInDirectory(directory);

HashSet<T> AllHashes = new(numFiles);

foreach (string file in Directory.EnumerateFiles(directory))
{
FileInfo fInfo = new(file);
T fileHash = fileToHash(fInfo);
AllHashes.Add(fileHash);
}
return AllHashes;
}
}
using System.Security.Cryptography;

namespace DirectoryDiff;

public static class FileUtils
{
private static readonly Lazy<SHA256> Sha256 = new(SHA256.Create);

public static byte[] GetFileHashBytes(FileInfo fInfo, SHA256 sha256)
{
using FileStream fileStream = fInfo.Open(FileMode.Open);
byte[] hash = sha256.ComputeHash(fileStream);
return hash;
}

public static byte[] GetFileHashBytes(FileInfo fInfo)
{
return GetFileHashBytes(fInfo, Sha256.Value);
}

public static string GetFileHashString(FileInfo fInfo, SHA256 sha256)
{
return HashBytesToString(GetFileHashBytes(fInfo, sha256));
}

public static string GetFileHashString(FileInfo fInfo)
{
return GetFileHashString(fInfo, Sha256.Value);
}

public static string HashBytesToString(byte[] hash)
{
return BitConverter.ToString(hash).Replace("-", "");
}

private static int GetNumFilesInDirectory(string directory)
{
return Directory.EnumerateFiles(directory).Count();
}

public static HashSet<T> GetFileHashesDirectory<T>(string directory, Func<FileInfo, T> fileToHash)
{
int numFiles = GetNumFilesInDirectory(directory);

HashSet<T> AllHashes = new(numFiles);

foreach (string file in Directory.EnumerateFiles(directory))
{
FileInfo fInfo = new(file);
T fileHash = fileToHash(fInfo);
AllHashes.Add(fileHash);
}
return AllHashes;
}
}
Here is the rest of the class
Joschi
Joschiā€¢3mo ago
Ok interesting. Why don't you just settle on a format for you hashes, like always use byte[] or string?
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
I don't know what I want yet I did some benchmarking to see if there was a big difference but there isn't enough of one to warrant using byte[] over string
Joschi
Joschiā€¢3mo ago
Interestingly you could also express your method in pure LINQ Directory.EnimeratFiles(directory).Select(x => new FileInfo(x)).Select(hashingFunc).ToHashSet() would be equivalent. šŸ˜„ Anyways if you use that often having a dedicated function is a good thing. But if you don't know what you actually want yet, I would suggest that you move on from trying to make the "best convenience function you can" to other more critical parts of your application. And then you may realise, what actually fits your use case the best.
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
I agree with all your points except that this is also a learning experience. If I didn't try to do it this backwards way I would not have seen the Func<> way of doing it that is much simpler than what i had in mind I am going to benchmark the non-linq and linq version and see if there is a noticable difference
Joschi
Joschiā€¢3mo ago
Memory consumption would be interesting in that benchmark, because you create a fixed size HashSet but there will be cases in which you will have less elements than your size. Because if that is not an expected scenario, there would be no need to use a HashSet.
Jason_Bjorn
Jason_BjornOPā€¢3mo ago
It will be a minutes because I have to figure out this git/hub thing first @Joschi
| Method | Mean | Error | StdDev | Allocated |
|------------------------------- |--------:|--------:|--------:|----------:|
| TestGetFileHashesDirectory | 12.37 s | 0.097 s | 0.091 s | 140.39 KB |
| TestGetFileHashesDirectoryLinq | 12.34 s | 0.077 s | 0.072 s | 132.2 KB |
| Method | Mean | Error | StdDev | Allocated |
|------------------------------- |--------:|--------:|--------:|----------:|
| TestGetFileHashesDirectory | 12.37 s | 0.097 s | 0.091 s | 140.39 KB |
| TestGetFileHashesDirectoryLinq | 12.34 s | 0.077 s | 0.072 s | 132.2 KB |
This is in a directory with ~140 image files most time is probably spent on computing the hash thanks for your help @Joschi
Want results from more Discord servers?
Add your server