我有一个项目,我试图在一个构造函数中填充一些数据:
public class ViewModel
{
public ObservableCollection<TData> Data { get; set; }
async public ViewModel()
{
Data = await GetDataTask();
}
public Task<ObservableCollection<TData>> GetDataTask()
{
Task<ObservableCollection<TData>> task;
//Create a task which represents getting the data
return task;
}
}
不幸的是,我得到一个错误:
修饰符async对此项无效
当然,如果我包装一个标准方法,并从构造函数调用它:
public async void Foo()
{
Data = await GetDataTask();
}
它工作得很好。同样,如果我用以前由内而外的方法
GetData().ContinueWith(t => Data = t.Result);
这也有用。我只是想知道为什么我们不能直接从构造函数中调用await。可能有很多(甚至明显的)边缘情况和理由反对它,我只是想不出任何一个。我也四处寻找解释,但似乎找不到任何解释。
构造函数的作用与返回构造类型的方法非常相似。async方法不能返回任何类型,它必须是“触发并忘记”void或Task。
如果类型T的构造函数实际返回Task<T>,我认为这将非常令人困惑。
如果async构造函数的行为与async void方法相同,那就破坏了构造函数的意义。构造函数返回后,您应该得到一个完全初始化的对象。不是一个在将来某个未定义的点上将被正确初始化的对象。也就是说,如果你足够幸运,异步初始化没有失败。
所有这些都只是猜测。但在我看来,使用异步构造函数的可能性带来的麻烦比它的价值要多。
如果你真的想要async void方法的“发射并忘记”语义(如果可能的话应该避免),你可以很容易地将所有代码封装在一个async void方法中,并从你的构造函数调用它,就像你在问题中提到的那样。
您的问题类似于创建文件对象并打开文件。事实上,有很多类在实际使用对象之前必须执行两个步骤:create + Initialize(通常称为类似于Open的东西)。
这样做的好处是构造函数可以是轻量级的。如果需要,可以在实际初始化对象之前更改一些属性。设置完所有属性后,将调用Initialize/Open函数来准备要使用的对象。这个Initialize函数可以是异步的。
缺点是您必须相信类的用户在使用类的任何其他函数之前会调用Initialize()。事实上,如果你想让你的类完全证明(傻瓜证明?),你必须检入Initialize()被调用的每个函数。
简化此操作的模式是将构造函数声明为私有,并创建一个公共静态函数,该函数将构造对象,并在返回构造的对象之前调用Initialize()。这样,您就会知道有权访问该对象的每个人都使用了Initialize函数。
该示例显示了一个模仿所需异步构造函数的类
public MyClass
{
public static async Task<MyClass> CreateAsync(...)
{
MyClass x = new MyClass();
await x.InitializeAsync(...)
return x;
}
// make sure no one but the Create function can call the constructor:
private MyClass(){}
private async Task InitializeAsync(...)
{
// do the async things you wanted to do in your async constructor
}
public async Task<int> OtherFunctionAsync(int a, int b)
{
return await ... // return something useful
}
使用方法如下:
public async Task<int> SomethingAsync()
{
// Create and initialize a MyClass object
MyClass myObject = await MyClass.CreateAsync(...);
// use the created object:
return await myObject.OtherFunctionAsync(4, 7);
}