13

次の (大幅に簡略化された) コードを検討してください。

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 などのポリモーフィズムで関連付け終了の再定義ができる理由の 1 つです)。 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

これらのタイプのチェックをジェネリック メソッドで行っている場合は、設計を再考します。メソッドは明らかに真にジェネリックではありません-もしそうなら、特定の型チェックは必要ありません...

このような状況は通常、再設計によってよりきれいに処理できます。1 つの代替手段として、適切な型のオーバーロードを提供することがよくあります。デリゲートを渡すという 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

ここ使えasますか?

T s = "hello" as T;
if(s != null)
    return s;
于 2010-02-15T22:07:07.447 に答える
1

これを行う「エレガントな」方法は考えられません。あなたが言うように、コンパイラは、条件が T の型が であることを保証したことを知ることができませんstring。その結果、 からstringT に変換する一般化された方法がないため、エラーであると想定する必要があります。 objectto Tは成功する可能性があるため、コンパイラはそれを許可します。

これをエレガントな方法で表現する必要があるかどうかはわかりません。状況によっては、このような明示的な型チェックを行う必要がある場所はわかりますが、実際にはちょっとしたハックであるため、煩雑にしたいと思います。そして、私はそれを目立たせたいと思います:「おい!私はここで何か変なことをしている!」

于 2010-02-15T22:42:39.523 に答える
0

さて、私はいくつかの異なる角度からそれを実行し、不足しました。私は、あなたの現在の実装が仕事を成し遂げるなら、あなたは勝利を収めて先に進むべきであると結論しなければならないでしょう。いくつかの難解な排出物を除いて、あなたが得たものはあなたが得たものです。

しかし、コンパイラーには、前のテストでTが文字列型であることが保証されていることを知る方法がありません。

うーん....私が間違っていなければ、ジェネリックは単なるコード生成です。コンパイラーは、呼び出し元のメソッドで見つかった個別のタイプごとに一致するメソッドを生成します。したがって、コンパイラ、呼び出されているオーバーロードの型引数を認識しています。また; もし私の考え違いでなければ。

しかし、全体として、私が見ることができることから、この場合はジェネリックを誤用していると思います。他の人が述べているように、要件を完全に指定するコードを投稿しない限り、名前を付けられないより適切な解決策があります。

ちょうど私の2ペソ...

于 2010-02-16T01:53:29.877 に答える