编辑1:忘记添加嵌套属性曲线球。
更新:我选择了@ mtazva的答案,因为这是我特定案例的首选解决方案。回想起来,我用一个非常具体的例子问了一个普遍的问题,我相信这最终会使每个人(或者可能只是我)混淆这个问题究竟是什么。我相信一般问题也得到了回答(参见战略模式答案和链接)。谢谢大家!
很大的转换语句smell我已经看到了一些关于如何使用dictionary that maps to functions执行此操作的链接。但我想知道是否有更好的(或更聪明的方法)来做到这一点?在某种程度上,这是一个我一直在脑海中徘徊的问题,但从来没有真正有一个很好的解决方案。
这个问题来自我之前提到的另一个问题:How to select all the values of an object's property on a list of typed objects in .Net with C#
以下是我正在使用的示例类(来自外部源):
public class NestedGameInfoObject
{
public string NestedName { get; set; }
public int NestedIntValue { get; set; }
public decimal NestedDecimalValue { get; set; }
}
public class GameInfo
{
public int UserId { get; set; }
public int MatchesWon { get; set; }
public long BulletsFired { get; set; }
public string LastLevelVisited { get; set; }
public NestedGameInfoObject SuperCoolNestedGameInfo { get; set; }
// thousands more of these
}
不幸的是,这是来自外部资源......想象一下来自侠盗猎车手的巨大数据转储。
我想得到这些对象列表的一小部分。想象一下,我们希望能够将您与一群朋友的游戏信息对象进行比较。一个用户的单个结果如下所示:
public class MyResult
{
public int UserId { get; set; } // user id from above object
public string ResultValue { get; set; } // one of the value fields from above with .ToString() executed on it
}
我希望用更易于管理的东西替换的一个例子(相信我,我不想维护这个怪物切换声明):
const int MATCHES_WON = 1;
const int BULLETS_FIRED = 2;
const int NESTED_INT = 3;
public static List<MyResult> GetMyResult(GameInfo[] gameInfos, int input)
{
var output = new List<MyResult>();
switch(input)
{
case MATCHES_WON:
output = gameInfos.Select(x => new MyResult()
{
UserId = x.UserId,
ResultValue = x.MatchesWon.ToString()
}).ToList<MyResult>();
break;
case BULLETS_FIRED:
output = gameInfos.Select(x => new MyResult()
{
UserId = x.UserId,
ResultValue = x.BulletsFired.ToString()
}).ToList<MyResult>();
break;
case NESTED_INT:
output = gameInfos.Select(x => new MyResult()
{
UserId = x.UserId,
ResultValue = x.SuperCoolNestedGameInfo.NestedIntValue.ToString()
}).ToList<MyResult>();
break;
// ad nauseum
}
return output;
}
所以问题是有没有合理的方法来管理这头野兽?我真正喜欢的是在初始对象发生变化(例如,添加更多游戏信息属性)的情况下获取此信息的动态方式。是否有更好的方法来构建它,这样就不那么笨拙了?
答案 0 :(得分:8)
我认为你的第一句话可能是最合理的解决方案:某种形式的字典映射值到方法。
例如,您可以定义静态Dictionary<int, func<GameInfo, string>>
,其中每个值(如MATCHES_WON)都将添加相应的lambda,该lambda将提取适当的值(假设您的常量等定义如示例所示):
private static Dictionary<int, Func<GameInfo, string>> valueExtractors =
new Dictionary<int, Func<GameInfo, string>>() {
{MATCHES_WON, gi => gi.MatchesWon.ToString()},
{BULLETS_FIRED, gi => gi.BulletsFired.ToString()},
//.... etc for all value extractions
};
然后,您可以使用此字典提取示例方法中的值:
public static List<MyResult> GetMyResult(GameInfo[] gameInfos, int input)
{
return gameInfo.Select(gi => new MyResult()
{
UserId = gi.UserId,
ResultValue = valueExtractors[input](gi)
}).ToList<MyResult>();
}
在此选项之外,您可能会使用数字和属性名称进行某种文件/数据库/存储查找,然后使用反射来提取值,但这显然不会执行。
答案 1 :(得分:5)
我认为这段代码已经失控了。您正在有效地使用常量来索引属性 - 这就是创建脆弱的代码,您希望使用某些技术 - 例如 - 反射,字典等 - 来控制增加的复杂性。
有效地,您现在使用的方法最终会得到如下代码:
var results = GetMyResult(gameInfos, BULLETS_FIRED);
另一种方法是定义一个允许你这样做的扩展方法:
var results = gameInfos.ToMyResults(gi => gi.BulletsFired);
这是强类型的,它不需要常量,切换语句,反射或任何神秘的东西。
只需编写这些扩展方法即可:
public static class GameInfoEx
{
public static IEnumerable<MyResult> ToMyResults(
this IEnumerable<GameInfo> gameInfos,
Func<GameInfo, object> selector)
{
return gameInfos.Select(gi => gi.ToMyResult(selector));
}
public static MyResult ToMyResult(
this GameInfo gameInfo,
Func<GameInfo, object> selector)
{
return new MyResult()
{
UserId = gameInfo.UserId,
ResultValue = selector(gameInfo).ToString()
};
}
}
这对你有用吗?
答案 2 :(得分:4)
您可以将反射用于这些目的。您可以实现自定义属性,标记您的属性等。此外,它是动态的方式来获取有关您的类的信息,如果它更改。
答案 3 :(得分:1)
如果您想管理切换代码,我会指出您设计模式书(GoF),并建议可能会查看Strategy
和可能Factory
等模式(当我们谈论一般情况下,你的情况不太适合工厂)并实施它们。
虽然在重构模式完成之后仍然需要将switch语句留在某处(例如,在你通过id选择策略的地方),代码将更加可维护和清晰。
关于一般的开关维护说,如果它们变成了野兽,我不确定它的最佳解决方案,因为你的案例陈述看起来有多么相似。
我100%确定你可以创建一些方法(可能是一个扩展方法)来接受所需的属性访问器lambda,这应该在生成结果时使用。
答案 4 :(得分:1)
如果您希望您的代码更通用,我同意字典或某种查找模式的建议。
您可以将函数存储在字典中,但它们似乎都执行相同的操作 - 从属性中获取值。这对于反思来说已经成熟。
我将所有属性存储在带有枚举的字典中(更喜欢枚举到const
)作为键,以及PropertyInfo - 或者不太喜欢的是一个描述属性名称的字符串 - 作为价值。然后,您可以在PropertyInfo对象上调用GetValue()
方法,以从对象/类中检索值。
这是一个示例,我将枚举值映射到类中的“相同命名”属性,然后使用反射从类中检索值。
public enum Properties
{
A,
B
}
public class Test
{
public string A { get; set; }
public int B { get; set; }
}
static void Main()
{
var test = new Test() { A = "A value", B = 100 };
var lookup = new Dictionary<Properties, System.Reflection.PropertyInfo>();
var properties = typeof(Test).GetProperties().ToList();
foreach (var property in properties)
{
Properties propertyKey;
if (Enum.TryParse(property.Name, out propertyKey))
{
lookup.Add(propertyKey, property);
}
}
Console.WriteLine("A is " + lookup[Properties.A].GetValue(test, null));
Console.WriteLine("B is " + lookup[Properties.B].GetValue(test, null));
}
您可以将const
值映射到属性的名称,与这些属性相关的PropertyInfo对象,将检索属性值的函数......您认为适合您的需求。
当然您需要一些映射 - 在某种程度上,您将依赖于您对特定属性的输入值(const
)映射。获取此数据的方法可能会为您确定最佳的映射结构和模式。
答案 5 :(得分:1)
我认为要走的路确实是从某个值(int)到某种某种知道如何提取值的函数的某种映射。 如果你真的想保持它的可扩展性,那么你可以轻松地添加一些而不需要触及代码,并且可能访问更复杂的属性(即嵌套属性,做一些基本的计算),你可能希望将它保存在一个单独的源中。
我认为一种方法是依靠脚本服务,例如评估一个简单的IronPython表达式来提取值......
例如,在文件中,您可以存储以下内容:
<GameStats>
<GameStat name="MatchesWon" id="1">
<Expression>
currentGameInfo.BulletsFired.ToString()
</Expression>
</GameStat>
<GameStat name="FancyStat" id="2">
<Expression>
currentGameInfo.SuperCoolNestedGameInfo.NestedIntValue.ToString()
</Expression>
</GameStat>
</GameStats>
然后,根据请求的统计信息,您总是最终检索一般的GameInfos。你可以通过以下方式获得某种foreach循环:
foreach( var gameInfo in gameInfos){
var currentGameInfo = gameInfo
//evaluate the expression for this currentGameInfo
return yield resultOfEvaluation
}
有关如何在.NET应用程序中嵌入IronPython脚本的示例,请参阅http://www.voidspace.org.uk/ironpython/dlr_hosting.shtml。
注意:使用这种东西时,有几件事你必须要小心:
答案 6 :(得分:0)
我无法解决您的切换问题,但您可以通过使用可自动映射所需字段的类来减少代码。查看http://automapper.org/。
答案 7 :(得分:0)
我不会首先编写GetMyResult
方法。它所做的只是将GameInfo
序列转换为MyResult
序列。使用Linq更容易,也更有表现力。
而不是打电话
var myResultSequence = GetMyResult(gameInfo, MatchesWon);
我只想打电话
var myResultSequence = gameInfo.Select(x => new MyResult() {
UserId = x.UserId,
ResultValue = x.MatchesWon.ToString()
});
为了使其更简洁,您可以在构造函数中传递UserId
和ResultValue
var myResultSequence =
gameInfo.Select(x => new MyResult(x.UserId, x.MatchesWon.ToString()));
仅当您看到选项过多时才重构。
答案 8 :(得分:0)
这是不使用反射的一种可能方式:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
public class GameInfo
{
public int UserId { get; set; }
public int MatchesWon { get; set; }
public long BulletsFired { get; set; }
public string LastLevelVisited { get; set; }
// thousands more of these
}
public class MyResult
{
public int UserId { get; set; } // user id from above object
public string ResultValue { get; set; } // one of the value fields from above with .ToString() executed on it
}
public enum DataType
{
MatchesWon = 1,
BulletsFired = 2,
// add more as needed
}
class Program
{
private static Dictionary<DataType, Func<GameInfo, object>> getDataFuncs
= new Dictionary<DataType, Func<GameInfo, object>>
{
{ DataType.MatchesWon, info => info.MatchesWon },
{ DataType.BulletsFired, info => info.BulletsFired },
// add more as needed
};
public static IEnumerable<MyResult> GetMyResult(GameInfo[] gameInfos, DataType input)
{
var getDataFunc = getDataFuncs[input];
return gameInfos.Select(info => new MyResult()
{
UserId = info.UserId,
ResultValue = getDataFunc(info).ToString()
});
}
static void Main(string[] args)
{
var testData = new GameInfo[] {
new GameInfo { UserId="a", BulletsFired = 99, MatchesWon = 2 },
new GameInfo { UserId="b", BulletsFired = 0, MatchesWon = 0 },
};
// you can now easily select whatever data you need, in a type-safe manner
var dataToGet = DataType.MatchesWon;
var results = GetMyResult(testData, dataToGet);
}
}
}
答案 9 :(得分:0)
纯粹基于大型switch语句的问题,值得注意的是,Cyclomatic Complexity度量标准有两种常用的变体。 “原始”将每个case语句计为一个分支,因此它将复杂度度量增加1 - 这导致由许多开关引起的非常高的值。 “variant”将switch语句计为单个分支 - 这实际上将其视为一系列非分支语句,这更符合控制复杂性的“可理解性”目标。