1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DynamicObject.TryConvertの呼ばれ方

Last updated at Posted at 2017-11-06

長い;読めぬ

こんなさむしんぐがあったとして、


class MyDynamic:DynamicObject
{
	public override bool TryConvert(ConvertBinder binder, out object result)
	{
		Console.WriteLine($"\tTryCovert is called!");
		result= Enumerable.Range(0, 10).ToArray();
		return true;
	}
}


static void Main(string[] args)
{
	dynamic d = new MyDynamic();

	int[] array = d;//1
	array = (int[]) d;//2

	IEnumerable<int> intIterator = d;//3
	intIterator = (IEnumerable<int>) d;//4
}

こんな風にすると、なぜか④だけInvalidCastExceptionが飛んで来るから注意しましょうって話。

何が起きたのか

動かすとわかるけど、①~③は想定通りTryConvertメソッドが呼ばれて動的に変換が解決されているコトがわかる。

けど、④だけはTryConvertが呼ばれずInvalidCastExceptionが飛んでくる。

どーしてこうなった

これらの挙動はExplicit reference conversionsの、bullet 3みれば書いてあって

From any class_type S to any interface_type T, provided S is not sealed and provided S does not implement T.

と定義されてるから、まず明示的な型変換は可能であるのだけど、逆にPermitted user-defined conversionsのbullet 3に、

Neither S0 nor T0 is an interface_type.

とあって、任意の型にpublic static implicit operator <interface_type> (...)public static explicit operator <interface_type>(...)みたいなことは出来ないよと言うことになる1

で、ここら辺の理由から、明示的な型変換を行った場合、interface-typeへのユーザー定義変換が動的型に対するコールサイト構築を行う際、全く考慮されてないんじゃないかなって。

で、結局考慮されてないわけだから、Interface-typeへの明示的な型変換は、一般的なC#の文脈で行われるってコトだと思う。

他方、暗黙の型変換が許容されるのは、右辺にある'd'が明示的な変換操作無しで変換可能であるかのように振る舞う必要があるため、コールサイト構築時にDynamicObject.TryConvertが呼ばれる様なコールサイト構築を行うのではと。

じゃーどーすりゃいいのか

答えはこれ以上無いほどシンプルで、変換したいinterfaceを付けてやれば良い。
先の例だとこんな感じ


	class MyEnumerableDynamic :DynamicObject,IEnumerable<int>
	{
		public IEnumerator<int> GetEnumerator()
		{
			Console.WriteLine("GetEnumerator was called.");

			return Enumerable.Range(0, 10).GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
			=> GetEnumerator();

		public override bool TryConvert(ConvertBinder binder, out object result)
		{
			Console.WriteLine($"\tTryCovert is called!");

			if (binder.Type == typeof(int))
			{
				result = 0;
				return true;
			}

			result = Enumerable.Range(0, 10).ToArray();
			return true;
		}

	}

こーすれば、一般的な型変換のコンテキストで動いて問題なく変換できる。

参考にしたサイト

DynamicObject.TryConvert not called when casting to interface type

On Dynamic Objects and DynamicObject

  1. 実際コンパイルエラーになる。蛇足ながらこれで来ちゃうと色々とマズいとは思うので妥当じゃ有ると思う。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?