我正在添加一些我希望在应用程序范围内使用的本地化逻辑,而不是多次创建它我决定为这个场景创建一个Generic方法而它可以工作,但我的Generics知识是有限的,它感觉不到最佳...
方法:
public List<T> Localize<T>(List<T> inputList, string propertyName)
{
var filteredList = new List<T>();
foreach (var item in inputList)
{
var clone = Mapper.Map<T, T>(item);
// "TranslationKey" is the fixed/same column name for every entity/table.
var translationKey = item.GetType().GetProperty("TranslationKey").GetValue(clone).ToString();
if (!string.IsNullOrEmpty(translationKey))
{
var prop = clone.GetType().GetProperty(propertyName);
prop.SetValue(clone, TranslateAsync(translationKey).Result);
}
filteredList.Add(clone);
}
return filteredList;
}
然后我会这样调用这个方法:
var items = Localize(itemsList, "Description"); // To localize "Description" prop
var people = Localize(peopleList, "Name"); // To localize "Name" prop of People list...
所以我的两个主要问题是:
答案 0 :(得分:1)
我可以优化我的方法实现吗?
那么,这取决于你所说的“优化”。我认为您可以将foreach
循环转换为LINQ Select
调用,然后调用Where
来过滤掉空值。
有没有办法在调用方法时无法对属性名称进行硬编码,或者是否有更好/更快的LINQ方式或者我缺少的东西?
我建议您在传递属性名称时使用nameof
运算符,而不是:
var items = Localize(itemsList, "Description");
你可以这样做:
var items = Localize(itemsList, nameof(Description));
这样,您可以避免对字符串进行硬编码!
答案 1 :(得分:1)
从您的设计看来,AES-GCM
可以用于为每个实体准确地本地化一个属性,因此以下内容没有意义:
TranslationKey
如果是这种情况 - 调用方法时不需要指定属性进行本地化。相反,使用这样的界面:
Localize(itemsList, "Description");
Localize(itemsList, "Title"); // cannot do that, because only description is localizable
您的public interface ITranslatable {
string TranslationKey { get; }
string TranslatedProperty { get; set; }
}
class MyEntity : ITranslatable {
public string TranslationKey { get; set; }
public string Description { get; set; }
// implicit implementation to avoid clutter
string ITranslatable.TranslatedProperty
{
get { return Description; }
set { Description = value; }
}
}
方法不再需要任何反映:
Localize
答案 2 :(得分:1)
您可以解决以下几点:
在泛型方法中使用类似item.GetType().GetProperty("TranslationKey").GetValue(clone)
的内容时,这意味着类型始终具有TranslationKey
属性。我建议使用一些接口和泛型约束来提高速度并添加编译时检查。例如:
public interface ITranslatable
{
string TranslationKey { get; }
}
用法:
public void Translate<T>(T value) where T : ITranslatable
{
var translationKey = value.TranslationKey;
...
}
但这可能无法实现,尤其是在使用第三方类型时。
PropertyInfo
您的代码似乎始终为传入的集合的每个元素访问相同的属性:
foreach (var item in items)
{
var clone = Mapper.Map<T, T>();
...
var prop = clone.GetType().GetProperty(propertyName);
...
}
您只需为foreach
之外的每个方法调用加载一次属性,这样可以节省一些时间(特别是在大集合上)。如果你想进一步优化速度,你也可以缓存给定类型的属性,但我不建议它作为启动器。
如果Mapper.Map<T, T>
可以实际获取并返回比T
更多派生的类型并隐藏propertyName
属性(例如,通过new
关键字),则可能无效
其他小问题是您分配filteredList
没有任何大小提示,但大小已知,大小为inputList
。这可以节省大量收藏的时间。此外,inputList
参数的类型可以是IReadOnlyCollection
,从而改进了签名以反映其所做的工作。
由于prop.SetValue(clone, TranslateAsync(translationKey).Result);
循环中存在foreach
副作用,我反对此处使用LINQ。您仍然可以从方法返回IReadOnlyCollection
或IReadOnlyList
,从而在使用位置上获得更好的流量。
public IReadOnlyCollection<T> Localize<T>(IReadOnlyCollection<T> inputList, string propertyName) where T : ITranslatable
{
var prop = typeof(T).GetProperty(propertyName);
var filteredList = new List<T>(inputList.Count);
foreach (var item in inputList)
{
var clone = Mapper.Map<T, T>(item);
// "TranslationKey" is the fixed/same column name for every entity/table.
var translationKey = clone.TranslationKey;
if (!string.IsNullOrEmpty(translationKey))
{
prop.SetValue(clone, TranslateAsync(translationKey).Result);
}
filteredList.Add(clone);
}
return filteredList;
}
答案 3 :(得分:1)
由于反射用于getter / setter,这可能是可以避免的,因此确实存在优化空间,更重要的是在将来更改对象的情况下使代码更安全。
第一个反射 item.GetType().GetProperty("TranslationKey").GetValue(clone)
表示类型T
始终具有属性TranslationKey
。只要所有对象都实现了正确的接口(或共享相同的基类),通用约束就可以强制执行
例如,Item类和People类都实现类似interface ITranslationEntity{string TranslationKey{get;}}
的内容,如果方法签名包含where T:ITranslationEntity
,则可以直接访问该属性
setter 可以使用相同的接口实现来完成,例如在接口上有一个SetTranslatedValue(string)
,或者setter可以实现为lambda public List<T> Localize<T>(List<T> inputList, Action<T,string> setter)
在这里使用后者作为一个例子,但在接口中使用它本身似乎是更好的选择。
一个小的额外更改可以使调用更容易,但取决于您是否可以将此方法放在静态类中,是使方法成为扩展方法。
也许最重要的部分是 async 调用在循环中同步执行(通过访问Result
)。也许实现不是真正的异步,但如果是,主要的优化是组合异步调用并等待任务的集合(并将结果设置在ContinueWith
最终结果:
static class SomeClass
{
public static List<T> Localize<T>(this List<T> inputList, Action<T,string> setter)
where T:ITranslationEntity
{
var clonedList = inputList.Select(Mapper.Map<T,T>).ToList();
var tasks = (from clone in clonedList
let key = clone.TranslationKey
where key != null
select TranslateAsync(key).ContinueWith(t=> setter(clone,t.Result))).ToArray();
Task.WaitAll(tasks);
return clonedList;
}
}
示例电话:
var items = itemsList.Localize((i,val) =>i.Description = val);
同样,setter只是一种替代方案,并且在其他方面实现可能更好,但主要的一点是尽可能避免反射,如果为多个项调用异步代码,则等待组合任务的结果。
答案 4 :(得分:0)
添加这个答案(来自其他答案)作为未来的另一种选择,这样我可以将Localize暴露给Singular和Multiple对象:
public List<T> Localize<T>(List<T> inputList, Action<T, string> setter) where T : ITranslatable
{
return inputList.Select(a => Localize(a, setter)).ToList();
}
public T Localize<T>(T input, Action<T, string> setter) where T : ITranslatable
{
var clone = Mapper.Map<T, T>(input);
var translationKey = clone.TranslationKey;
if (!string.IsNullOrEmpty(translationKey))
{
setter(clone, TranslateAsync(translationKey).Result);
}
return clone;
}
界面:
public interface ITranslatable
{
string TranslationKey{ get; }
}
这样打电话:
Localize(items, (i, val) => i.Description= val); // etc.