Обновление двух общих функций для использования Generics
Используя Microsoft Unit Test Wizard, он создает объекты Accessor, если вам нужно проверить непубличное свойство в другом проекте. В своих модульных тестах я создаю вспомогательные функции, чтобы не повторять один и тот же код только в каждом методе модульного теста. В настоящее время у меня есть два теста, которые практически идентичны, за исключением того, что один принимает стандартный объект, а другой - версию Accessor. Так как Accessor основан на стандартной версии, у меня должна быть одна функция, и я полагаю, что должен иметь возможность использовать Generics для выполнения. Проблема пытается перепечатывать и компилировать сбои.
Вот две существующие функции:
// Common function to create a new test record with standard Account object
internal static void CreateAccount(out Account account, bool saveToDatabase)
{
DateTime created = DateTime.Now;
string createdBy = _testUserName;
account = new Account(created, createdBy);
account.Notes = Utilities.RandomString(1000);
if (saveToDatabase)
account.Create();
}
// Common function to create a new test record with Account_Accessor
internal static void CreateAccount(out Account_Accessor account, bool saveToDatabase)
{
DateTime created = DateTime.Now;
string createdBy = _testUserName;
account = new Account_Accessor(created, createdBy);
account.Notes = Utilities.RandomString(1000);
if (saveToDatabase)
account.Create();
}
Я попытался изменить подпись комбинированной функции на:
internal static void CreateAccount<T>(out T account, bool saveToDatabase) {...}
но не удалось правильно переписать T в Account или Account_Accessor. Какие-либо предложения?
2 ответа
Вы должны добавить ограничение к универсальной функции, потому что это два метода:
account.Notes = Utilities.RandomString(1000);
account.Create();
Я предлагаю вам добавить некоторый интерфейс с этими двумя методами и добавить наследование от него к вашим двум классам. Ограничение должно быть следующим:
where T : YourNewInterface
Об ограничениях вы можете прочитать по http://msdn.microsoft.com/en-us/library/bb384067.aspx
ОБНОВИТЬ
public interface IAccount
{
string Notes { get; set; }
void Create();
void Init(DateTime created, string createdBy);
}
public class Account : IAccount
{
public string Notes
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void IAccount.Create()
{
throw new NotImplementedException();
}
void IAccount.Init(DateTime created, string createdBy)
{
throw new NotImplementedException();
}
}
public class Account_Accessor : IAccount
{
string IAccount.Notes
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void IAccount.Create()
{
throw new NotImplementedException();
}
void IAccount.Init(DateTime created, string createdBy)
{
throw new NotImplementedException();
}
}
class Program
{
internal static void CreateAccount<T>(out T account, bool saveToDatabase) where T : IAccount,new()
{
DateTime created = DateTime.Now;
string createdBy = _testUserName;
account = new T();
account.Init(created, createdBy);
account = (T)Activator.CreateInstance(typeof(T), new object[] { created, createdBy });
account.Notes = Utilities.RandomString(1000);
if (saveToDatabase)
account.Create();
}
static void Main(string[] args)
{
Account acc;
Account_Accessor acc2;
CreateAccount(out acc, false);
CreateAccount(out acc2, false);
}
}
Вот несколько комментариев о моем примере:
1. Я заменил CreateInstance
добавляя new()
ограничение.
2. Поскольку ограничение new() не может иметь параметров из-за общих ограничений.NET, я добавил Init()
метод к IAccount
интерфейс.
3. Init
метод не должен вызываться клиентским кодом Account
класс, поэтому мы определяем метод как частный и явно для IAccount.
4. Из-за new()
ограничение вы должны предоставить конструктор без параметров для Account
, Если вы сделаете это, ваш клиентский код не должен вызывать этот ctor без параметров.
Что касается меня, я бы ушел Activator.CreateInstance
как есть. Это хороший обходной путь для ограничений общего new()
ограничение
Вот мое мнение о том, чтобы сделать этот метод общим.
public abstract class BaseAccount
{
public string Notes;
public virtual void Create() { ... }
}
public class Account : BaseAccount { ... }
public class Account_Accessor : BaseAccount { ... }
internal static void CreateAccount<T>(out T account, bool saveToDatabase) where T : BaseAccount
{
DateTime created = DateTime.Now;
string createdBy = _testUserName;
account = (T)Activator.CreateInstance(typeof(T), new object[] { created, createdBy });
account.Notes = Utilities.RandomString(1000);
if (saveToDatabase)
account.Create();
}
Я предполагаю, что Account и Account_Accessor достаточно похожи, чтобы иметь общий класс hiearchy или реализовывать один и тот же интерфейс. В этом примере я предоставил абстрактный класс, из которого они оба получены, но вместо этого очень легко сделать это с помощью интерфейса. Тем не менее, полная реализация должна быть сделана в обоих классах.
Зная это, я могу ограничить универсальный метод так, чтобы T был только потомком BaseAccount. Таким образом, я могу получить доступ к члену из этого базового класса, не зная реального типа T.