通用类型推断,Fluent Api,具有预先声明的类型

AAA*_*ddd 9 c# generics type-inference

我正在开发一个Fluent Api来提供相当可配置的服务,并且只是尝试为下面的问题提供一个简洁的解决方案.

我有一个这样的课

public class WindowVm : DialogResultBase<MyReturnType>
Run Code Online (Sandbox Code Playgroud)

一切都很好,但是任何人都可以想到一种方法来实现以下,而不必详细说明给定的第二种通用​​类型即

public IDialogWithResult<TViewModel, TSomeReturnType> DialogWithResult<TViewModel,TSomeReturnType>(object owner = null)
        where TViewModel : DialogResultBase<TSomeReturnType>
Run Code Online (Sandbox Code Playgroud)

IDialogWithResult<TViewModel, TSomeReturnType>即使我必须在2个陈述中做到这一点,我真的只对结果感兴趣

所以我可以打电话

.DialogWithResult<WindowVm>()
Run Code Online (Sandbox Code Playgroud)

我知道所有信息都在那里并在编译时声明,我也知道这是部分推理及其全部或全部.然而,我只是想知道是否有一些技巧,而不必重新声明

.DialogWithResult<WindowVm, ResultType>(); 
Run Code Online (Sandbox Code Playgroud)

此外,我有一个方法需要ResultType作为(你猜对了)一个结果类型

ResultType MyResult =  ...DialogWithResult<WindowVm, ResultType>()
                         .ShowModal(); 
Run Code Online (Sandbox Code Playgroud)

我的意思ResultType是,在游戏的这一点上真的是多余的,因为它已经被宣布了WindowVm.如果消费者不必去寻找它(即使它意味着超过一步),那将是很好的

Cod*_*ler 9

是的,编译器的所有信息来推断该类型TSomeReturnType传递时WindowVmTViewModel.但是,允许减少泛型(.DialogWithResult<WindowVm>())的参数列表的主要障碍是它可能与具有相同名称但只有一个泛型类型参数的重载方法冲突.例如,如果您在类中有以下方法:

public IDialogWithResult<TViewModel, TSomeReturnType> DialogWithResult<TViewModel,TSomeReturnType>(object owner = null)
        where TViewModel : DialogResultBase<TSomeReturnType>

public IDialogWithResult<TViewModel> DialogWithResult<TViewModel>(object owner = null)
        where TViewModel : DialogResultBase<MyReturnType>
Run Code Online (Sandbox Code Playgroud)

编码时编译器应该调用哪一个.DialogWithResult<WindowVm>()

这就是为什么这种简化的语法可能不会在C#中引入的原因.

但是,您仍然可以选择使调用尽可能简单.DialogWithResult<WindowVm>().我不是这个解决方案的粉丝,但如果您的Fluent Api电话的简洁性非常重要,那么您可以使用它.该解决方案基于TSomeReturnType传递TViewModel类型的反射和运行时提取类型:

public class YourClass
{
    public dynamic DialogWithResult<TViewModel>(object owner = null)
    {
        //  Searching for DialogResultBase<TSomeReturnType> in bases classes of TViewModel
        Type currType = typeof(TViewModel);
        while (currType != null && currType != typeof(DialogResultBase<>))
        {
            if (currType.IsGenericType && currType.GetGenericTypeDefinition() == typeof(DialogResultBase<>))
            {
                break;
            }

            currType = currType.BaseType;
        }
        if (currType == null)
        {
            throw new InvalidOperationException($"{typeof(TViewModel)} does not derive from {typeof(DialogResultBase<>)}");
        }

        Type returnValueType = currType.GetGenericArguments()[0];

        //  Now we know TViewModel and TSomeReturnType and can call DialogWithResult<TViewModel, TSomeReturnType>() via reflection.
        MethodInfo genericMethod = GetType().GetMethod(nameof(DialogWithResultGeneric));
        if (genericMethod == null)
        {
            throw new InvalidOperationException($"Failed to find {nameof(DialogWithResultGeneric)} method");
        }

        MethodInfo methodForCall = genericMethod.MakeGenericMethod(typeof(TViewModel), returnValueType);
        return methodForCall.Invoke(this, new [] { owner } );
    }

    public IDialogWithResult<TViewModel, TSomeReturnType> DialogWithResultGeneric<TViewModel, TSomeReturnType>(object owner = null)
        where TViewModel : DialogResultBase<TSomeReturnType>
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

我们DialogWithResult<TViewModel>()用一个泛型类型参数声明了新方法TViewModel.然后我们搜索基DialogResultBase<T>类.如果发现我们提取类型TSomeReturnTypeType.GetGenericArguments()调用.最后DialogWithResultGeneric<TViewModel, TSomeReturnType>通过反射调用原始方法.请注意,我已经改名为原来的方法DialogWithResultGeneric,这样GetMethod()不会抛出AmbiguousMatchException.

现在在您的程序中,您可以将其称为:

.DialogWithResult<WindowVm>()
Run Code Online (Sandbox Code Playgroud)

缺点是没有什么能阻止你在错误的类型上调用它(一个不继承DialogResultBase<T>):

.DialogWithResult<object>()
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您不会收到编译错误.只有在抛出异常的运行时才会识别该问题.您可以使用此答案中描述的技术解决此问题.简而言之,您应该声明非泛型DialogResultBase并将其设置为以下基础DialogResultBase<T>:

public abstract class DialogResultBase
{
}

public class DialogResultBase<T> : DialogResultBase
{
    //  ...
}
Run Code Online (Sandbox Code Playgroud)

现在您可以在DialogWithResult<TViewModel>()类型参数上添加约束:

public dynamic DialogWithResult<TViewModel>(object owner = null)
    where TViewModel : DialogResultBase
Run Code Online (Sandbox Code Playgroud)

现在.DialogWithResult<object>()会导致编译错误.

同样,我不是我提出的解决方案的忠实粉丝.但是,只有C#功能才能实现您的要求.


Gra*_*x32 8

正如你和@CodeFuller所观察到的那样,在C#中不可能进行部分推理.

如果你正在寻找一些不那么邪恶而不是动态的东西,你可以使用扩展方法和自定义类的组合来获得你需要的类型,而无需直接引用返回类型.

在下面的示例中,我使用扩展方法DialogResultBase<T>来推断返回类型,然后我返回一个包含泛型方法的辅助类DialogWithResult<WindowVm>.

仍然不漂亮,但大致符合你的要求.

关于推理的有趣观点.每个参数只能用于推断单个类型.如果要多次传递相同的参数,可以从中推断出多种类型.即如果您将相同的参数传递给两个参数,则(T myList, List<TItem> myListAgain)可以推断列表类型和项类型.

public class Class2
{
    public static void DoStuff()
    {
        var dialogResult = default(WindowVm).GetReturnType().DialogWithResult<WindowVm>();

    }

}


public class MyReturnType { }
public class DialogResultBase<T> : IDialogWithResult<T> { }
public interface IDialogWithResult<TSomeReturnType> { }

public class WindowVm : DialogResultBase<MyReturnType> { }

public class DialogResultHelper<TSomeReturnType>
{
    public IDialogWithResult<TSomeReturnType> DialogWithResult<TViewModel>() where TViewModel : DialogResultBase<TSomeReturnType>, new()
    {
        return new TViewModel();
    }
}

public static class Extensions
{
    public static DialogResultHelper<T> GetReturnType<T>(this DialogResultBase<T> dialogResultBase)
    {
        return new DialogResultHelper<T>();
    }

}
Run Code Online (Sandbox Code Playgroud)