C
C#2w ago
Adam

Making my functions more generic

right now i have several functions that do essentially the same thing, the only difference being the type they take in i have two data classes that have different data but both share one variable, a Tag class i have two different functions to go through my database and get these data objects with a matching Tag, how would i go about combining these into one? making them both inherit from a class with a Tag or could i make the functions themselves generic?
16 Replies
Tvde1
Tvde12w ago
could you show us the functions? I would define an interface which has the property that it needs from both, and then you can do
public Task DoSomething<TEntity>(TEntity entity)
where TEntity : INeeded
{
entity.Id = ...;
}

interface TEntity
{
public int Id { get; }
}
public Task DoSomething<TEntity>(TEntity entity)
where TEntity : INeeded
{
entity.Id = ...;
}

interface TEntity
{
public int Id { get; }
}
Adam
Adam2w ago
public Dictionary<string, Stat> StatsWithTag(string tagName)
{
Dictionary<string, Stat> stats = new();
foreach (var stat in statsDB.Stats)
{
foreach (var statTag in stat.Tags)
{
if (statTag.TagName == tagName)
stats.Add(stat.StatName, new(stat));
}
}

return stats;
}
public Dictionary<string, Stat> StatsWithTag(string tagName)
{
Dictionary<string, Stat> stats = new();
foreach (var stat in statsDB.Stats)
{
foreach (var statTag in stat.Tags)
{
if (statTag.TagName == tagName)
stats.Add(stat.StatName, new(stat));
}
}

return stats;
}
other one is the same but with another class instead of Stat
Tvde1
Tvde12w ago
a better way to do this is:
public Dictionary<string, Stat> StatsWithTag(string tagName)
{
return statsDB.Stats
.Where(stat => stat.TagName == tagName)
.ToDictionary(stat => stat.StatName);
}
public Dictionary<string, Stat> StatsWithTag(string tagName)
{
return statsDB.Stats
.Where(stat => stat.TagName == tagName)
.ToDictionary(stat => stat.StatName);
}
I believe this gives the same result and it is recommended to make this async, but I will leave this up to you
Adam
Adam2w ago
i was under the impression loops are a bit more performant than LINQ, but i'm not sure if it matters in my use case so that does look a lot cleaner i haven't familiarized myself with async, why do you say it's recommended here? and why is this a task and what does where TEntity : INeeded do?
Tvde1
Tvde12w ago
it is a little bit complicated, but in this case, the LINQ is more performant. Your example does: 1. Get all Stats from the database 2. Loop over all the stats 3. If it matches the tag name, add to dictionary The example I showed uses IQueryable, which entity framework is built on to do the following 1. Only take from the database, all the Stats which match the tag name. (This will do the query:
SELECT * FROM Stats
WHERE TagName = 'your-tag-name'
SELECT * FROM Stats
WHERE TagName = 'your-tag-name'
2. it loops over only the results which are found and makes a dictionary
Adam
Adam2w ago
oh i see that's sick, ty
Tvde1
Tvde12w ago
only when you do * ToList() * ToDictionary() * First() etc, does your query get executed. So any .Where() that you do, will be translated to SQL for better performance
Adam
Adam2w ago
gotcha gotcha not sure if you saw this, could you explain the TEntity interface implementation?
Tvde1
Tvde12w ago
for your use case I wouldn't deduplicate it if you wanted to, it would look something like
interface ITaggable
{
public string TagName { get; }
public string Name { get; }
}

public Dictionary<string, T> StatsWithTag<T>(DbSet<T> dbSet, string tagName)
{
return dbSet
.Where(stat => stat.TagName == tagName)
.ToDictionary(stat => stat.Name);
}

class Stat : ITaggable
{
// your properties
public string StatName { get; set; }
...

// required because of the interface
public string TagName { get; }

// Required so the interface can get the name
public string Name => StatName;
}

class SomethingElse : ITaggable
{
// your properties
public string MySomethingName { get; set; }
...

// required because of the interface
public string TagName { get; }

// Required so the interface can get the name
public string Name => MySomethingName;
}
interface ITaggable
{
public string TagName { get; }
public string Name { get; }
}

public Dictionary<string, T> StatsWithTag<T>(DbSet<T> dbSet, string tagName)
{
return dbSet
.Where(stat => stat.TagName == tagName)
.ToDictionary(stat => stat.Name);
}

class Stat : ITaggable
{
// your properties
public string StatName { get; set; }
...

// required because of the interface
public string TagName { get; }

// Required so the interface can get the name
public string Name => StatName;
}

class SomethingElse : ITaggable
{
// your properties
public string MySomethingName { get; set; }
...

// required because of the interface
public string TagName { get; }

// Required so the interface can get the name
public string Name => MySomethingName;
}
than you can do
var statsByName = StatsWithTag(statsDB.Stats, "my-tag");
var othersByName = StatsWithTag(statsDB.SomethingElse, "my-other-tag");
var statsByName = StatsWithTag(statsDB.Stats, "my-tag");
var othersByName = StatsWithTag(statsDB.SomethingElse, "my-other-tag");
But I wouldn't make it more complicated than it needs to be
Adam
Adam2w ago
okay i see i see tyty i'll try this out oh and is there a similarly more performant way to implement this function? i haven't changed the Stat class to implement an interface yet so keep that in mind i use the dict of stats i got from the other function and then take in another dict of stats that i want to add stats to, but only add them if the dict doesn't already have them
public void AddMissingStatsWithTag(Dictionary<string, Stat> dict, string tagName)
{
Dictionary<string, Stat> fullDict = StatsWithTag(tagName);

foreach (KeyValuePair<string, Stat> pair in fullDict)
{
if (!dict.ContainsKey(pair.Key))
{
dict.Add(pair.Key, pair.Value);
}
}
}
public void AddMissingStatsWithTag(Dictionary<string, Stat> dict, string tagName)
{
Dictionary<string, Stat> fullDict = StatsWithTag(tagName);

foreach (KeyValuePair<string, Stat> pair in fullDict)
{
if (!dict.ContainsKey(pair.Key))
{
dict.Add(pair.Key, pair.Value);
}
}
}
alternatively a way to do this innately without a function, i've looked for that but couldn't find a good solution
Tvde1
Tvde12w ago
This one is fine It's a bit strange why there would be things missing but alright 😁
Joschi
Joschi2w ago
An minor improvement would be to use dict.TryAdd() instead of the if followed by add
Adam
Adam2w ago
ohh nice tip ty i call this function to add all stats basically since each data object will need a different set of stats when my data objects are initialised since i don't know how to do it at construction (in unity)
Adam
Adam2w ago
how would i do this if Tag isn't a single string but a list?
No description
Adam
Adam2w ago
also had a problem here i'm not sure about
No description
No description
Tvde1
Tvde14d ago
you have your ) a bit misplaced
return statsDB.Stats
.Where(s => s.PrimaryTags.Contains(tagName))
.ToList();
return statsDB.Stats
.Where(s => s.PrimaryTags.Contains(tagName))
.ToList();