考虑以下(严重简化的)代码:
public T Function<T>() {
if (typeof(T) == typeof(string)) {
return (T) (object) "hello";
}
...
}
首先施放到object
然后转移到T
是荒谬的。但编译器无法知道先前的测试保证T
的类型为string
。
在C#中实现此行为的最优雅,惯用的方式是什么(包括摆脱愚蠢的typeof(T) == typeof(string)
,因为T is string
无法使用)?
附录: .net中没有返回类型差异,所以你不能让函数重载来输入字符串(顺便说一下,这只是一个例子,但其中一个原因多态中的关联结束重定义,例如UML,不能在c#中完成。显然,以下情况会很好,但它不起作用:
public T Function<T>() {
...
}
public string Function<string>() {
return "hello";
}
具体示例1:因为针对特定类型测试的通用函数不是通用的,所以有几次攻击,我将尝试提供更完整的示例。考虑Type-Square设计模式。以下是一个片段:
public class Entity {
Dictionary<PropertyType, object> properties;
public T GetTypedProperty<T>(PropertyType p) {
var val = properties[p];
if (typeof(T) == typeof(string) {
(T) (object) p.ToString(this); // magic going here
}
return (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(val);
}
}
具体示例2:考虑解释器设计模式:
public class Expression {
public virtual object Execute() { }
}
public class StringExpression: Expression {
public override string Execute() { } // Error! Type variance not allowed...
}
现在让我们在Execute中使用泛型来允许调用者强制返回类型:
public class Expression {
public virtual T Execute<T>() {
if(typeof(T) == typeof(string)) { // what happens when I want a string result from a non-string expression?
return (T) (object) do_some_magic_and_return_a_string();
} else if(typeof(T) == typeof(bool)) { // what about bools? any number != 0 should be True. Non-empty lists should be True. Not null should be True
return (T) (object) do_some_magic_and_return_a_bool();
}
}
}
public class StringExpression: Expressiong {
public override T Execute<T>() where T: string {
return (T) string_result;
}
}
答案 0 :(得分:6)
如果您使用通用方法进行这些类型的检查,我会重新考虑您的设计。该方法显然不是真正的泛型 - 如果是,则不需要特定的类型检查......
这种情况通常可以通过重新设计更清晰地处理。一种替代方案通常是提供适当类型的过载。还存在避免类型特定行为的其他设计备选方案,例如Richard Berg's suggestion of passing in a delegate。
答案 1 :(得分:3)
using System;
using System.Collections.Generic;
using System.Linq;
namespace SimpleExamples
{
/// <summary>
/// Compiled but not run. Copypasta at your own risk!
/// </summary>
public class Tester
{
public static void Main(string[] args)
{
// Contrived example #1: pushing type-specific functionality up the call stack
var strResult = Example1.Calculate<string>("hello", s => "Could not calculate " + s);
var intResult = Example1.Calculate<int>(1234, i => -1);
// Contrived example #2: overriding default behavior with an alternative that's optimized for a certain type
var list1 = new List<int> { 1, 2, 3 };
var list2 = new int[] { 4, 5, 6 };
Example2<int>.DoSomething(list1, list2);
var list1H = new HashSet<int> { 1, 2, 3 };
Example2<int>.DoSomething<HashSet<int>>(list1H, list2, (l1, l2) => l1.UnionWith(l2));
}
}
public static class Example1
{
public static TParam Calculate<TParam>(TParam param, Func<TParam, TParam> errorMessage)
{
bool success;
var result = CalculateInternal<TParam>(param, out success);
if (success)
return result;
else
return errorMessage(param);
}
private static TParam CalculateInternal<TParam>(TParam param, out bool success)
{
throw new NotImplementedException();
}
}
public static class Example2<T>
{
public static void DoSomething(ICollection<T> list1, IEnumerable<T> list2)
{
Action<ICollection<T>, IEnumerable<T>> genericUnion = (l1, l2) =>
{
foreach (var item in l2)
{
l1.Add(item);
}
l1 = l1.Distinct().ToList();
};
DoSomething<ICollection<T>>(list1, list2, genericUnion);
}
public static void DoSomething<TList>(TList list1, IEnumerable<T> list2, Action<TList, IEnumerable<T>> specializedUnion)
where TList : ICollection<T>
{
/* stuff happens */
specializedUnion(list1, list2);
/* other stuff happens */
}
}
}
/// I confess I don't completely understand what your code was trying to do, here's my best shot
namespace TypeSquarePattern
{
public enum Property
{
A,
B,
C,
}
public class Entity
{
Dictionary<Property, object> properties;
Dictionary<Property, Type> propertyTypes;
public T GetTypedProperty<T>(Property p)
{
var val = properties[p];
var type = propertyTypes[p];
// invoke the cast operator [including user defined casts] between whatever val was stored as, and the appropriate type as
// determined by the domain model [represented here as a simple Dictionary; actual implementation is probably more complex]
val = Convert.ChangeType(val, type);
// now create a strongly-typed object that matches what the caller wanted
return (T)val;
}
}
}
/// Solving this one is a straightforward application of the deferred-execution patterns I demonstrated earlier
namespace InterpreterPattern
{
public class Expression<TResult>
{
protected TResult _value;
private Func<TResult, bool> _tester;
private TResult _fallback;
protected Expression(Func<TResult, bool> tester, TResult fallback)
{
_tester = tester;
_fallback = fallback;
}
public TResult Execute()
{
if (_tester(_value))
return _value;
else
return _fallback;
}
}
public class StringExpression : Expression<string>
{
public StringExpression()
: base(s => string.IsNullOrEmpty(s), "something else")
{ }
}
public class Tuple3Expression<T> : Expression<IList<T>>
{
public Tuple3Expression()
: base(t => t != null && t.Count == 3, new List<T> { default(T), default(T), default(T) })
{ }
}
}
答案 2 :(得分:1)
你能在这里使用as
吗?
T s = "hello" as T;
if(s != null)
return s;
答案 3 :(得分:1)
我想不出一种“优雅”的方式来做到这一点。如您所说,编译器无法知道条件确保T的类型为string
。因此,它必须假设,因为没有从string
转换为T的通用方法,所以这是一个错误。 object
到T 可能成功,因此编译器允许它。
我不确定我是否想要一种优雅的方式来表达这一点。虽然我可以看到在某些情况下需要在哪里进行这样的显式类型检查,但我认为我希望它很麻烦,因为它确实有点像黑客。而且我希望它能够突出:“嘿!我在这里做了一些奇怪的事情!”
答案 4 :(得分:0)
好的,我从几个不同的角度对它进行了一次尝试并且做得很短。我必须得出结论,如果你当前的实施完成工作,你应该取胜并继续前进。除了一些神秘的排放,你得到的就是你得到的。
<击> 撞击>
<击>嗯......如果我没弄错的话,泛型就是代码。编译器为调用方法中找到的每个不同类型生成匹配方法。所以编译器确实知道被调用的重载的类型参数。再次;如果我没弄错的话。但是编译器无法知道 以前的测试保证T是 输入字符串。
但总的来说,我认为你在这种情况下滥用通用,从我所看到的,并且正如其他人所说,有更合适的解决方案.....除非你发布完全指定你的代码,否则这些解决方案是不可能的要求。
只是我的2比索......