13

考虑以下(高度简化的)代码:

public T Function<T>() {
    if (typeof(T) == typeof(string)) {
        return (T) (object) "hello";
    }
    ...
}

先转换为object,然后转换为有点荒谬T。但是编译器无法知道先前的测试保证T是 type 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;
  }
}
4

5 回答 5

5

如果您以通用方法进行这些类型的检查,我会重新考虑您的设计。该方法显然不是真正的通用- 如果是,您将不需要特定的类型检查......

像这样的情况通常可以通过重新设计更干净地处理。一种替代方法通常是提供适当类型的重载。也存在其他避免特定类型行为的设计替代方案,例如Richard Berg 建议传入委托

于 2010-02-15T22:05:18.510 回答
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) })
        { }
    }
}
于 2010-02-16T09:19:19.307 回答
1

我想不出一种“优雅”的方式来做到这一点。正如您所说,编译器无法知道条件已确保 T 的类型为string。因此,它必须假设,由于没有通用的方法可以将 from 转换string为 T,所以这是一个错误。 objectto T可能会成功,因此编译器允许它。

我不确定我是否想要一种优雅的方式来表达这一点。虽然我可以看到在某些情况下需要在哪里进行这样的显式类型检查,但我认为我希望它很麻烦,因为它确实有点像 hack。而且我希望它突出:“嘿!我在这里做一些奇怪的事情!”

于 2010-02-15T22:42:39.523 回答
1

可以在这里使用as吗?

T s = "hello" as T;
if(s != null)
    return s;
于 2010-02-15T22:07:07.447 回答
0

好的,我从几个不同的角度进行了测试,但都失败了。我不得不得出结论,如果您当前的实施能够完成工作,您应该赢得胜利并继续前进。没有一些奥术排放,你得到的就是你得到的。

但是编译器无法知道前面的测试保证 T 是字符串类型。

嗯....如果我没记错的话,泛型只是代码生成。编译器为调用方法中找到的每个不同类型生成一个匹配方法。所以编译器确实知道被调用的重载的类型参数。再次; 如果我没有记错的话。

但总的来说,我认为你在这种情况下滥用了泛型,据我所见,正如其他人所说,有更合适的解决方案......除非你发布完全指定你的要求的代码,否则这些解决方案是不可命名的。

只是我的2比索...

于 2010-02-16T01:53:29.877 回答