The Scope of this Project is just a console application and I get a pretty annoying
The error I get when trying to pass the value:
Argument type 'lambda expression' is not assignable to parameter type 'DungeonRunner.Ability'
The function where I want to pass the parameter:
public void UseSpell(Ability ability)
{
var SpellToUse = this.CharClass.GetAbilities();
//Get all Abilites
this.CharClass.GetAbilities();
if (this.MP == 0)
{
Console.WriteLine("You cannot use advanced Abilities, drink a Mana Potion to restore Mana");
var UsedSpell = SpellToUse.First();
this.MP -= UsedSpell.ManaCost1; //This line is unnecessary because the first spell costs nothing
}
else if(this.MP >= 0)
{
var UsedSpell = SpellToUse.Find(abilityName => abilityName.SpellName1 == ability.SpellName1);
this.MP -= UsedSpell.ManaCost1;
}
The class I reference to:
namespace DungeonRunner
{
public class Ability
{
private int ManaCost;
private int SpellDamage;
private string SpellName;
public Ability(int manaCost, int spellDamage, string spellName)
{
ManaCost = manaCost;
SpellDamage = spellDamage;
SpellName = spellName;
}
public int ManaCost1
{
get => ManaCost;
set => ManaCost = value;
}
public int SpellDamage1
{
get => SpellDamage;
set => SpellDamage = value;
}
public string SpellName1
{
get => SpellName;
set => SpellName = value;
}
}
}
This is the value I try to pass:
`MyCharacter.UseSpell(ability => ability.SpellName == spellname)`;
The question is: How can I possibly optimize this so that the error goes away.
It may be that I'll need to change the paramter. But I dont think it's needed.
Assuming the list with all abilities is called abilities you can write the following code;
Ability usedAbility = abilities.FirstOrDefault(x => x.SpellName1 == spellname);
if(usedAbility != null)
{
MyCharacter.UseSpell(usedAbility);
}
With FirstOrDefault you're getting the first Ability with the spellname. I added the null check because if there is no Ability with that name you will get null from FirstOrDefault.
There are actually two ways you can do.
The first is provide the actual Ability to your method and let it do something with that instance:
public void UseSpell(Ability ability)
{
// do something with the provided ability
}
This way your client has to search for the correct ability. To do so it has to know all abilities:
UseSpell(myAbilities.FirstOrDefault(x => x.Spellname == "MySpell"));
The other opportunity is that UseSpell searches for an Ability itself based on some condition, which you can provide by a delegate. However that assumes that UseSpell has access to all abilities in order to search for the "right" one:
public void UseSpell(Func<Ability, bool> predicate)
{
// get all abilities
var allAbilties = ...;
// get the one that matches the condition
var ability = allAbilities.FirstOrDefault(predicate)
}
Now the client only provides the criterion, but not the entire list of abilities:
UseSpell(x => x.Spellname == "MySpell");
So depending on where your list of all abilites is located, either the one or the other approach might be better. The result is the same in both cases. However the semantics are different.
Reference is wrong. Do the following
MyCharacter.Where(ability => ability.SpellName == spellname).Foreach(ability => UseSpell(ability));
Related
I have to following C#-code quite a lot:
public void UpdateDB(Model.ResultContext db)
{
Model.Planning.Part dbPart = db.Parts.Where(/*someClause*/).FirstOrDefault();
//The awkward part
if (dbPart.Number != Number)
{
dbPart.Number = Number;
}
if (dbPart.NumberShort != NumberShort)
{
dbPart.NumberShort = NumberShort;
}
if (dbPart.Designation != Designation)
{
dbPart.Designation = Designation;
}
}
It is obviously kind of awkward to check every field and wrap it in if != then set
Yes, the check is needed because otherwise the database sees everything as changed columns.
The fields to set are auto-Properties:
public class Part
{
[MaxLength(36), MinLength(1)]
public string Number { get; set; } = null!;
[MaxLength(80)]
public string Designation { get; set; } = null!;
}
and I don't want to write an explicit setter for every field which of course could do the checking before setting.
So what I thought of is some Method ´SetIfChanged´ which is called like this to make the code more readable and less error-prone:
//Options
dbPart.SetIfChanged(dbPart.Number, this.Number);
dbPart.SetIfChanged(dbPart.Number = this.Number);
dbPart.SetIfChanged(Number, this.Number);
I think something like that is possible with expressions or lambdas but to be honest... I'm stuck with the syntax of declaring and calling such a method
Anybody can help me out?
Unfortunately, C# is lacking a number of things to help you with this (e.g. property refs or extension methods on reference objects), but you can use Reflection to help with this. It is likely to be quite slow, however.
With a method that takes a lambda, you can write a set method:
public static void SetIfDifferent<T>(Expression<Func<T>> getterFnE, T newVal) {
var me = (MemberExpression)getterFnE.Body;
var target = me.Expression;
var targetLambda = Expression.Lambda(target);
var prop = me.Member;
var oldVal = getterFnE.Compile().Invoke();
if ((oldVal == null && newVal != null) || !oldVal.Equals(newVal)) {
var obj = targetLambda.Compile().DynamicInvoke();
prop.SetValue(obj, newVal);
}
}
This would be used like:
SetIfDifferent(() => dbPart.Number, Number);
SetIfDifferent(() => dbPart.NumberShort, NumberShort);
SetIfDifferent(() => dbPart.Designation, Designation);
This would be slow because of the need to compile the Expression trees and use DynamicInvoke. One way to speed it up would be to pass in a setter and getter lambda instead, but that leads to as much duplication as your original code.
If you would be willing to pass the object and name of the property instead, you could use:
public static T GetValue<T>(this MemberInfo member, object srcObject) => (T)member.GetValue(srcObject);
public static void SetIfDifferent2<TObj, TField>(this TObj obj, string fieldName, TField newVal) {
var prop = typeof(TObj).GetProperty(fieldName);
var oldVal = prop.GetValue<TField>(fieldName);
if ((oldVal == null && newVal != null) || !oldVal.Equals(newVal))
prop.SetValue(obj, newVal);
}
Which you could use like:
dbPart.SetIfDifferent2(nameof(dbPart.Number), Number);
dbPart.SetIfDifferent2(nameof(dbPart.NumberShort), NumberShort);
dbPart.SetIfDifferent2(nameof(dbPart.Designation), Designation);
Unfortunately, it requires repeating dbPart unless you are willing to just put in the field name (e.g. "Number") but that will cause runtime errors if the field changes.
You could also cache the PropertyInfo instead of looking it up with GetProperty, but that is generally pretty fast and caching probably isn't worth it.
Well if you really need checking (lets say at the end you want to know if anything has been changed or not) you can use Reflection and loop through properties. but in your case no check is needed.
take this for instance:
if (dbPart.Number != Number)
{
dbPart.Number = Number;
}
true) if the value is different you are setting the new one
false) means that the new value and the old value are the same, so doesn't hurt to set it again
If you want to know if anything has changed at the end:
bool changed = false;
var type = dbPart.GetType();
foreach(var (PropertyInfo)pi in type.GetProperties()
{
if(pi.GetValue(dbPart) != newValue)
{
changed = true;
pi.SetValue(dbPart, newValue);
}
}
or you can do something like:
bool changed = dbPart.Number != Number || dbPart.Designation != Designation;
dbPart.Number = Number;
dbPart.Designation = Designation;
I've searched in vain mainly because I don't think I know what to look for. I think I need to use the Find method for Lists in C# to do what I want, but I can't seem to get it right. This leads me to think one of two obvious scenarios is happening:
1) I'm going about this the wrong way (my money is on this one).
2) I'm going about this the right way, but I just don't understand the syntax for finding something.
Also, just to answer some of the "Why are you..." or "Why haven't you..." questions: If you can't tell, I'm figuring this out as I go along - and this is about the extent of what I've been able to teach myself thus far.
Here is what I'm trying to do:
First, I have a list<FitHeaderCard> object called HeaderBlock. and it is populated by this custom type:
public class FitHeaderCard
{
public string keyword;
public string value;
public string comment;
//Constructor
public FitHeaderCard()
{}
public FitHeaderCard(string fitHeaderCard)
{ // input: 80 byte string containing 3 fixed width delimited values.
keyword = fitHeaderCard.Substring(0, 8).Trim();
value = fitHeaderCard.Substring(10, 20).Trim();
comment = fitHeaderCard.(33,47).Trim();
}
public FitHeaderCard(FitHeaderCard fitHeaderCard)
{ //clone a FitHeaderCard
keyword = fitHeaderCard.keyword;
value = fitHeaderCard.value;
comment = fitHeaderCard.comment;
}
}
The header cards are fed into a list when read from a file on my hard drive (using a BinaryReader).
The FitHeader object code is as follows:
public class FitHeader
{
public string headerCard;
public string headerSize;
public FitHeaderCard fitHeaderCard = new FitHeaderCard();
public List<FitHeaderCard> HeaderBlock = new List<FitHeaderCard>();
//Constructor
public FitHeader()
{
}
public FitHeader(string _headerCard)
{
headerCard = _headerCard;
}
public FitHeaderCard FitHeaderCard
{
get;
private set;
}
//Methods
public void AddHeaderCard(string _headerCard)
{
FitHeaderCard = new FitHeaderCard(_headerCard);
HeaderBlock.Add(FitHeaderCard);
}
public List<FitHeaderCard> GetHeader()
{
return HeaderBlock;
}
public int GetHeaderSize();
{
return headerSize = //some convoluted math but it works!
}
//This is what I'm having trouble with
public FitHeaderCard GetFitHeaderCard(string _keyword)
{
HeaderBlock.Find("BITPIX");
fitHeaderCard = new FitHeaderCard(fitHeaderCard); // clone constructor
return fitHeaderCard;
}
}
So the GetFitHeaderCard method is supposed to take in a keyword, and return the the entire FitHeaderCard object found in the HeaderBlock list.
Optionally, I'd love to just get the Value back, possibly in a separate method, but for now, I can live with just referencing the value of FitHeaderCard.Value
Thanks in advance for your help! I look forward to face palming myself when an elegant and obvious answer is revealed to this newbie .Net coder!
The Find method of List requires a predicate(i.e. a function that takes in an element, does some calculation on it, and returns a boolean). Right now you're passing a string. That's not a predicate. What you might do instead is create either a separate method, anonymous function, or lambda that matches what I just specified:
(separate method; could also be local function)
...
//This is what I'm having trouble with
public FitHeaderCard GetFitHeaderCard(string _keyword)
{
fitHeaderCard = HeaderBlock.Find(FilterByBitPix);
fitHeaderCard = new FitHeaderCard(fitHeaderCard); // clone constructor
return fitHeaderCard;
}
public FitHeaderCard FilterByBitPix(FitHeaderCard item)
{
return item.keyword == "BITPIX";
}
}
OR (anonymous function)
...
//This is what I'm having trouble with
public FitHeaderCard GetFitHeaderCard(string _keyword)
{
fitHeaderCard = HeaderBlock.Find(delegate (FitHeaderCard item) { return item.keyword == "BITPIX"); };
fitHeaderCard = new FitHeaderCard(fitHeaderCard); // clone constructor
return fitHeaderCard;
}
}
OR (lambda)
...
//This is what I'm having trouble with
public FitHeaderCard GetFitHeaderCard(string _keyword)
{
fitHeaderCard = HeaderBlock.Find(item => item.keyword == "BITPIX");
fitHeaderCard = new FitHeaderCard(fitHeaderCard); // clone constructor
return fitHeaderCard;
}
}
List<FitHeaderCard> matches = HeaderBlock.Where(p=>p.SomePublicStringPropertyInYourObject.Contains(searchString));
You want to code with C#, my advice: learn linq queries ASAP. It is very powerful for extracting what you need from lists.
Given that you have a List<>, there are a number of LINQ-based queries you can use to extract this information. In your particular example, this is probably the simplest:
var returnValue = HeaderBlock.FirstOrDefault(hb => hb.keyword.Equals("BITPIX"))?.value;
As the name suggests FirstOrDefault, this will return either the first item it finds in the list that matches, or the default value (which in this case is NULL because it is a list of objects).
There are other LINQ extensions you could use, such as:
First, which throws an exception if it can't find the keword;
var first = HeaderBlock.FirstOrDefault(hb => hb.keyword.Equals("BITPIX")).value;
Where, which returns a collection;
var foundItems = HeaderBlock.Where(hb => hb.keyword.Equals("BITPIX"));
Single, which is similar to First, but will throw an exception if there is more than one;
var single = HeaderBlock.Single(hb => hb.keyword.Equals("BITPIX")).value;
and SingleOrDefault, which again is similar to Single but will return NULL if it can't find anything.
var singleOrDefault = HeaderBlock.SingleOrDefault(hb => hb.keyword.Equals("BITPIX"))?.value;
LINQ is really important and incredibly powerful, and there is a lot of literature available to help you (admittedly, I personally found the official documentation quite hard to read, and hard to grasp the actual syntax in usage).
Having spent a long time solving this problem, I wanted to share the solution.
Background
I maintain a large web application with the primary function of managing orders. It is an MVC over C# application using EF6 for data.
There are LOTS of search screens. The search screens all have multiple parameters and return different object types.
The Problem
Every search screen had:
A ViewModel with the search parameters
A Controller method to handle the Search event
A method to pull the correct data for that screen
A method to apply all the search filters to the dataset
A method to convert the results into a NEW results ViewModel
The Results ViewModel
This adds up quickly. We have about 14 different search screens, which means about 84 models & methods to handle these searches.
My Goal
I wanted to be able to create a class, analogous to the current search parameter ViewModel, that would inherit from a base SearchQuery class such that my Controller could simply trigger the search to run to populate a Results field of the same object.
An Example of My Ideal State (Because It's a Bear To Explain)
Take the following class structure:
public class Order
{
public int TxNumber;
public Customer OrderCustomer;
public DateTime TxDate;
}
public class Customer
{
public string Name;
public Address CustomerAddress;
}
public class Address
{
public int StreetNumber;
public string StreetName;
public int ZipCode;
}
Let's assume I have lots of those records in a queryable format--an EF DBContext object, an XML object, whatever--and I want to search them. First, I create a derived class specific to my ResultType(in this case, Order).
public class OrderSearchFilter : SearchQuery
{
//this type specifies that I want my query result to be List<Order>
public OrderSearchFilter() : base(typeof(Order)) { }
[LinkedField("TxDate")]
[Comparison(ExpressionType.GreaterThanOrEqual)]
public DateTime? TransactionDateFrom { get; set; }
[LinkedField("TxDate")]
[Comparison(ExpressionType.LessThanOrEqual)]
public DateTime? TransactionDateTo { get; set; }
[LinkedField("")]
[Comparison(ExpressionType.Equal)]
public int? TxNumber { get; set; }
[LinkedField("Order.OrderCustomer.Name")]
[Comparison(ExpressionType.Equal)]
public string CustomerName { get; set; }
[LinkedField("Order.OrderCustomer.CustomerAddress.ZipCode")]
[Comparison(ExpressionType.Equal)]
public int? CustomerZip { get; set; }
}
I use attributes to specify what field/property of the target ResultType any given search field is linked to, as well as the comparison type (== < > <= >= !=). A blank LinkedField means that the name of the search field is the same as the name of the target object field.
With this configured, the only things I should need for a given search are:
A populated search object like the one above
A data source
No other scenario-specific coding should be required!
The Solution
For starters, we create:
public abstract class SearchQuery
{
public Type ResultType { get; set; }
public SearchQuery(Type searchResultType)
{
ResultType = searchResultType;
}
}
We'll also create the attributes we used above to define the search field:
protected class Comparison : Attribute
{
public ExpressionType Type;
public Comparison(ExpressionType type)
{
Type = type;
}
}
protected class LinkedField : Attribute
{
public string TargetField;
public LinkedField(string target)
{
TargetField = target;
}
}
For each search field, we'll need to know not only WHAT search is done, but also WHETHER the search is done. For example, if the value of "TxNumber" is null, we wouldn't want to run that search. So we create a SearchField object that contains, in addition to the actual search value, two expressions: one that represents performing the search, and one that validates whether the search should be applied.
private class SearchFilter<T>
{
public Expression<Func<object, bool>> ApplySearchCondition { get; set; }
public Expression<Func<T, bool>> SearchExpression { get; set; }
public object SearchValue { get; set; }
public IQueryable<T> Apply(IQueryable<T> query)
{
//if the search value meets the criteria (e.g. is not null), apply it; otherwise, just return the original query.
bool valid = ApplySearchCondition.Compile().Invoke(SearchValue);
return valid ? query.Where(SearchExpression) : query;
}
}
Once we have created all our filters, all we need to do is loop through them and call the "Apply" method on our dataset! Easy!
The next step is creating the validation expressions. We'll do this based on the Type; every int? is validated the same as every other int?.
private static Expression<Func<object, bool>> GetValidationExpression(Type type)
{
//throw exception for non-nullable types (strings are nullable, but is a reference type and thus has to be called out separately)
if (type != typeof(string) && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)))
throw new Exception("Non-nullable types not supported.");
//strings can't be blank, numbers can't be 0, and dates can't be minvalue
if (type == typeof(string )) return t => !string.IsNullOrWhiteSpace((string)t);
if (type == typeof(int? )) return t => t != null && (int)t >= 0;
if (type == typeof(decimal? )) return t => t != null && (decimal)t >= decimal.Zero;
if (type == typeof(DateTime?)) return t => t != null && (DateTime?)t != DateTime.MinValue;
//everything else just can't be null
return t => t != null;
}
This was all I needed for my application, but there is definitely more validation that could be done.
The search expression is slightly more complicated and required a parser to "De-qualify" Field/Property names (there's probably a better word, but if so, I don't know it). Basically, if I specified "Order.Customer.Name" as a linked field and I'm searching through Orders, I need to turn that into "Customer.Name" because there is no Order Field inside an Order object. Or at least I hope not. :) This isn't certain, but I considered it better to accept and correct fully-qualified object names than to support that edge case.
public static List<string> DeQualifyFieldName(string targetField, Type targetType)
{
var r = targetField.Split('.').ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r;
}
This is just straight text parsing, and returns the Field name in "levels" (e.g. "Customer"|"Name").
All right, let's get our search expression together.
private Expression<Func<T, bool>> GetSearchExpression<T>(
string targetField, ExpressionType comparison, object value)
{
//get the property or field of the target object (ResultType)
//which will contain the value to be checked
var param = Expression.Parameter(ResultType, "t");
Expression left = null;
foreach (var part in DeQualifyFieldName(targetField, ResultType))
left = Expression.PropertyOrField(left == null ? param : left, part);
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
//join the expressions with the specified operator
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, param);
}
Not so bad! What we're trying to create is, for example:
t => t.Customer.Name == "Searched Name"
Where t is our ReturnType--an Order, in this case. First we create the parameter, t. Then, we loop through the parts of the property/field name until we have the full title of the object we're targeting (naming it "left" because it's the left side of our comparison). The "right" side of our comparison is simple: the constant provided by the user.
Then we create the binary expression and turn it into a lambda. Easy as falling off a log! If falling off a log required countless hours of frustration and failed methodologies, anyway. But I digress.
We've got all the pieces now; all we need is a method to assemble our query:
protected IQueryable<T> ApplyFilters<T>(IQueryable<T> data)
{
if (data == null) return null;
IQueryable<T> retVal = data.AsQueryable();
//get all the fields and properties that have search attributes specified
var fields = GetType().GetFields().Cast<MemberInfo>()
.Concat(GetType().GetProperties())
.Where(f => f.GetCustomAttribute(typeof(LinkedField)) != null)
.Where(f => f.GetCustomAttribute(typeof(Comparison)) != null);
//loop through them and generate expressions for validation and searching
try
{
foreach (var f in fields)
{
var value = f.MemberType == MemberTypes.Property ? ((PropertyInfo)f).GetValue(this) : ((FieldInfo)f).GetValue(this);
if (value == null) continue;
Type t = f.MemberType == MemberTypes.Property ? ((PropertyInfo)f).PropertyType : ((FieldInfo)f).FieldType;
retVal = new SearchFilter<T>
{
SearchValue = value,
ApplySearchCondition = GetValidationExpression(t),
SearchExpression = GetSearchExpression<T>(GetTargetField(f), ((Comparison)f.GetCustomAttribute(typeof(Comparison))).Type, value)
}.Apply(retVal); //once the expressions are generated, go ahead and (try to) apply it
}
}
catch (Exception ex) { throw (ErrorInfo = ex); }
return retVal;
}
Basically, we just grab a list of fields/properties in the derived class (that are linked), create a SearchFilter object from them, and apply them.
Clean-Up
There's a bit more, of course. For example, we're specifying object links with strings. What if there's a typo?
In my case, I have the class check whenever it spins up an instance of a derived class, like this:
private bool ValidateLinkedField(string fieldName)
{
//loop through the "levels" (e.g. Order / Customer / Name) validating that the fields/properties all exist
Type currentType = ResultType;
foreach (string currentLevel in DeQualifyFieldName(fieldName, ResultType))
{
MemberInfo match = (MemberInfo)currentType.GetField(currentLevel) ?? currentType.GetProperty(currentLevel);
if (match == null) return false;
currentType = match.MemberType == MemberTypes.Property ? ((PropertyInfo)match).PropertyType
: ((FieldInfo)match).FieldType;
}
return true; //if we checked all levels and found matches, exit
}
The rest is all implementation minutia. If you're interested in checking it out, a project that includes a full implementation, including test data, is here. It's a VS 2015 project, but if that's an issue, just grab the Program.cs and Search.cs files and throw them into a new project in your IDE of choice.
Thanks to everyone on StackOverflow who asked the questions and wrote the answers that helped me put this together!
i have this class
public class ConnectionResult
{
private int connectionPercentage;
public int ConnectPercentage
{
get { return connectionPercentage; }
}
public ConnectionResult(int ip)
{
// Check connection and set connectionPercentage
}
}
and i have a manager that gets several lists of ConnectionResult and count each value greater then a specific number determined by configuration. my implementation is so:
public class CurrentConnections
{
private static CurrentConnections inst;
private CurrentConnections()
{
}
public static CurrentConnections GetInstance
{
get
{
if (inst != null)
{
inst = new CurrentConnections();
}
return inst;
}
}
public int CountActiveConnections(params List<ConnectionResult>[] conns)
{
int rtVal = 0;
foreach (List<ConnectionResult> connectionResult in conns)
{
foreach (var currConn in connectionResult)
{
if (currConn.ConnectPercentage > ACCEPTABLE_CONNECTION)
{
rtVal++;
}
}
}
return rtVal;
}
}
but i want to make it better, so i started to write it in linq and i got to
conns.Count(x => x.Count(y => y.ConnectPercentage > ACCEPTABLE_CONNECTION));
but this gives me an error of Cannot implicitly convert type 'int' to 'bool'.
is there a way to count it in linq or do i have to stay with what i wrote?
btw, i'm new to linq
You're using Count twice, and I don't think you want to. I think you just want:
return conns.SelectMany(list => list)
.Count(conn => conn.ConnectPercentage > ACCEPTABLE_CONNECTION);
The SelectMany call is to flatten the "array of lists" into a single sequence of connections.
John Skeet's answer is excellent, but to address the error that you're seeing, the query would be:
conns.Sum(x => x.Count(y => y.ConnectPercentage > ACCEPTABLE_CONNECTION));
Count accepts a function which returns bool and returns the number of items from the collection which meet that criteria.
Sum accepts a function which returns int (among others), and returns the sum of the results of the expression applied to each item.
Of course, whether you select every item from each subset and then count them up (like John Skeet suggests), or you count the items from each subset and then add up the counts (like my code suggests), the result will be exactly the same.
return conns.SelectMany(x=> x).Where(conn => conn.ConnectPercentage > ACCEPTABLE_CONNECTION).;
This seems to come up alot in my code, i'm wondering if there is some way of removing the switch statement, or if there is a more elegant way of doing it?
public class MetaData
{
public string AlbumArtist { get; set; }
public string AlbumTitle { get; set; }
public string Year { get; set; }
public string SongTitle { get; set; }
public static MetaData CreateMetaDataFrom(IEnumerable<TextFrame> textFrames)
{
var metaData = new MetaData();
foreach (var frame in textFrames)
{
switch (frame.Descriptor.ID)
{
case "TPE1":
metaData.AlbumArtist = frame.Content;
break;
case "TALB":
metaData.AlbumTitle = frame.Content;
break;
case "TIT2":
metaData.SongTitle = frame.Content;
break;
case "TYER":
metaData.Year = frame.Content;
break;
}
}
return metaData;
}
}
This is related to an object-oriented approach. The usual method to get rid of if's or case's is to use a lookup table of criteria and effects. There are other techniques that use this same idea, such as data-directed programming (http://en.wikipedia.org/wiki/Data-directed_programming) and dispatch tables (http://en.wikipedia.org/wiki/Dispatch_table). Many language implementations use type dispatch tables to implement virtual method calls.
The lookup table could be a hashtable populated with lambda functions as so:
Dictionary<string, Func<MetaData, string, string>> lookup = new Dictionary<string, Func<MetaData, string, string>>();
lookup["TPE1"] = (m, v) => m.AlbumArtist = v;
lookup["TALB"] = (m, v) => m.AlbumTitle = v;
lookup["TIT2"] = (m, v) => m.SongTitle = v;
lookup["TYER"] = (m, v) => m.Year = v;
then you assign fields of metaData inside your loop as:
lookup[frame.Descriptor.ID](metaData, frame.Content);
From your code I conclude that IEnumerable<TextFrame> has always 4 members
so you could just write (haven't tried it so check for the syntax):
public static MetaData CreateMetaDataFrom(IEnumerable<TextFrame> textFrames)
{
return new MetaData()
{
metaData.AlbumArtist = textFrames.Where(frame => frame.Descriptor.ID = "TPE1").SingleOrDefault().Content,
metaData.AlbumTitle = textFrames.Where(frame => frame.Descriptor.ID = "TALB").SingleOrDefault().Content,
metaData.SongTitle = textFrames.Where(frame => frame.Descriptor.ID = "TIT2").SingleOrDefault().Content;
metaData.Year = textFrames.Where(frame => frame.Descriptor.ID = "TYER").SingleOrDefault().Content;
};
}
You might want to look at implementing the Strategy Pattern. DimeCasts.Net have an excellent video tutorial that may help.
I was tempted to suggest the Strategy Pattern but you may need a slight variation. Consider writing a method in the TextFrame class, lets call it putContent(MetaData).
Then create subclasses of TextFrame each representing a different Type of Frame. Each subclass will override the putContent(Metadata) method and do its appropraite logic.
PseudoCode Example for TPE1:
Metadata putContent(MetaData md){
md.AlbumArtist = Content;
return md;
}
You MetaData code will then change to:
var metaData = new MetaData();
foreach (var frame in textFrames)
{
metaData = frame.putContent(metaData);
}
return metaData;
Of course, to create the TextFrames them selves will then require a Factory so this is not the end of the story.
It seems like you know what the types will be before hand (using a switch) so why not just retrieve the values as required, without a for switch.
Examples are when using a hashtable, and you know what fields will be available, just use the fields.
ig you are not sure if the field is available, a simple test will be sufficiant if the list contains the value.
You can then even write a helper function to check and return the value if the list has the value.
What you really have here is a four-way setter. The canonical refactoring here is "Replace Parameter with Explicit Methods" (p285 of Refactoring by Martin Fowler). The Java example he gives is changing:
void setValue(String name, int value) {
if (name.equals("height")) {
_height = value;
return;
}
if (name.equals("width")) {
_width = value;
return;
}
}
to:
void setHeight(int arg) {
_height = arg;
}
void setWidth(int arg) {
_width = arg;
}
Assuming that the caller of CreateMetaDataFrom() knows what it's passing in, you could skip the switch/case and use the actual setters for those properties.