Надолу към генеричен тип - c #

Имам следните класове:

public interface IFile { }

public class File : IFile { }

public class FTPFile : File { }

public interface IFileManager<T> where T : IFile
{
void SaveFile(T file);
}

internal class FTPFileManager : IFileManager<FTPFile>
{
public void SaveFile(FTPFile file) { }
}

Имам няколко типа файлове и мениджъри, определени в моя проект, но се опитах да го опростя. В класа ми в завода имам следния метод, по който не може да се хвърля FTPFileManager да се IFileManager<IFile> но интересно може да хвърли FTPFileManager да се IFileManager<FTPFile>, И в двата случая няма грешка при компилирането, но по време на изпълнение тя хвърля грешка.

public class FileManagerFactory
{
public static IFileManager<IFile> GetFileManager(IFile file)
{
if (file is FTPFile)
return (IFileManager<IFile>)new FTPFileManager(); //No compile error but in runtime it throws casting error
}
}

Отговори:

1 за отговор № 1

Един от начините да направите това е да използвате следния модел:

public interface IFile { }

public class FTPFile : IFile { }

public interface IFileManager
{
void SaveFile(IFile file);

bool Accepts(IFile file);
}

public abstract class FileManager<TFile>
: IFileManager
where TFile : IFile
{
protected abstract void SaveFile(TFile file);

public void SaveFile(IFile file)
{
var cast = (TFile)file; // will throw a compile exception if wrong one

this.SaveFile(cast);
}

public bool Accepts(IFile file)
{
return file is TFile;
}
}

internal class FTPFileManager : FileManager<FTPFile>
{
protected sealed override void SaveFile(FTPFile file)
{
Console.WriteLine("Saved file");
}
}

public static IFileManager GetFileManager(IFile file)
{
var managers = new List<IFileManager> { new FTPFileManager() };

return managers.Single(manager => manager.Accepts(file));
}

3 за отговор № 2

Ще можеш да хвърлиш FTPFileManager да се IFileManager<IFile> ако IFileManager бяха коварващи, т.е. декларирани като:

public interface IFileManager<out T> where T : IFile

Това обаче няма да се компилира IFileManager има T като вход параметър на един от неговите методи. За да може интерфейсът да бъде ковариационен в T, трябва да има T само като вид връщане параметри.

Това има смисъл във вашата ситуация, защото FTPFileManager трябва да бъде даден случай на FTPFile, Ако можете да напишете:

var ftpManager = new FtpFileManager();
IFileManager<IFile> fileManager = ftpManager;
fileManager.SaveFile(new SomeOtherFileType());

След това FTP файловия мениджър ще очаква един FTPFile но вместо това да се предаде нещо друго. Поради тази причина, втората линия отказва да състави.


Обърнете внимание, че това също така подчертава понижаването на риска (или, най-малко, свалянето на информация, без да е много ясно защо го правите).

return (IFileManager<IFile>)new FTPFileManager();

вместо

return new FTPFileManager();

превърнахте грешка в компилацията във време на изпълнение.


0 за отговор № 3
 internal class FTPFileManager : IFileManager<IFile>
{
public void SaveFile(IFile file) { }
}
public class FileManagerFactory
{
public static IFileManager<IFile> GetFileManager(IFile file)
{
if (file is FTPFile)
return new FTPFileManager();
}
}

Опитайте по-горе. Ще работи.