Nəyə görə <T> siyahısından miras qalmırsınız?

Proqramlarımı planlaşdırarkən tez-tez fikirləşməyə başlayıram:

Futbol komandası yalnız oyunçuların siyahısıdır. Buna görə də, mən onu istifadə etməlidir:

 var football_team = new List<FootballPlayer>(); 

Bu siyahının sırası oyunçuların sıralanma sırasıdır.

Ancaq başa düşürəm ki, komandaların qeydə alınması lazım olan oyunçuların sadə siyahısı ilə yanaşı digər xüsusiyyətləri var. Məsələn, bu mövsümün ümumi bal sayıları, cari büdcə, vahid rəng, string , komandanın adını göstərən və s.

Beləliklə, düşünürəm:

Futbol komandası oyunçuların siyahısı ilə tam eynidır, lakin əlavə olaraq ad ( string ) və ümumi bal ( int ) var. NET futbol komandalarının saxlanılması üçün bir sinif təmin etmir, buna görə də öz sinifimə gələcək. Ən uyğun və müvafiq mövcud olan List<FootballPlayer> , mən onu devralın:

 class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal } 

Ancaq təlimatçı deyir ki, List<T> devralmamalıyıq . Mən bu təlimatda iki yolla tamamilə utandım.

Niyə olmasın?

Göründüyü kimi List performans üçün optimallaşdırılmışdır . Necə olar? Siyahıa davam edərsənsə, nə performans məsələləri List ? Nə tam pozulur?

Gördüyüm bir səbəbdən, List Microsoft tərəfindən təmin edilir və onu nəzarət etmirəm, ona görə də "açıq API" açıldıqdan sonra onu dəyişə bilmirəm . " Amma başa düşmürəm. Bir ictimai API nədir və niyə məni narahat edir? Mənim cari layihəmizdə bu ictimaiyyətin API olmadığı və çətin olacağı təqdirdə, bu təlimatı təhlükəsiz olaraq görmürsənmi? Mən List devralırsam və çıxdıqda ictimaiyyətə bir API lazımdır Hansı çətinliklər var?

Niyə belə vacibdir? Siyahı bir siyahıdır. Nə dəyişə bilər? Nə dəyişə bilərəm?

Və nəhayət, Microsoft mənim List miras qalmasını istəmirsə, nə üçün onlar sealed sinfi etmədilər?

Mən başqa nə istifadə etməliyəm?

Göründüyü kimi, xüsusi kolleksiyalar üçün, Microsoft List genişləndirilməli olan Collection dərsini təqdim etdi. Amma bu sinif çox çılpaqdır və məsələn, AddRange , məsələn, çox faydalı bir şey yoxdur. Jvitor83 cavabı bu xüsusi metodun yerinə yetirilməsini təmin edir, lakin AddRange dən daha yavaş nə qədər AddRange ?

Collection miras qaldıqdan daha çox List edirəm və heç bir fayda görmürəm. Əlbətdə ki, Microsoft mənə heç bir səbəbdən əlavə iş verməməyi xahiş etməzdi, buna görə bir şeyə yanlış anlayıram deyə hiss etmirəm və hiss etmirəm və Collection devralması həqiqətən problemim üçün doğru bir həll deyil.

IList tətbiqi kimi təkliflər gördüm. Yalnız deyil. Bunlar mənə bir şey gətirməyən şablon kodunun onlarla xətti.

Nəhayət, bəziləri List bir şeyə sarmağı təklif edir:

 class FootballTeam { public List<FootballPlayer> Players; } 

Bununla əlaqədar iki problem var:

  • Bu mənim kodu lazımsız olaraq ətraflı şəkildə təqdim edir. İndi my_team.Players.Count yerine my_team.Count . Xoşbəxtlikdən, C # ilə endeksleme şəffaflığı yaratmaq üçün endeksleyicileri müəyyən edə və Daxili List bütün metodları göndərə bilərəm ... Amma bu bir çox kod! İşləyən hər şey üçün nə alacağam?

  • Yalnız bir məna vermir. Futbol komandasında oyunçuların "siyahısı" yoxdur. Bu oyunçuların siyahısı. Bilmirsiniz: "John McFutaller, SomeTeam oyunçularına qatıldı." Siz deyirsiniz: "Yəhya Birimə qatıldı." Siz simli simvollara bir məktub əlavə etmirsiniz, simli bir məktub əlavə edərsiniz. Kitabxana kitablarına bir kitab əlavə etmirsiniz, kitabxanaya kitab əlavə edirsiniz.

Anlayıram ki, "başlıq altındakı" baş verən hadisələr, "XY-nin daxili siyahıya əlavə edilməsi" demək olar, lakin bu, dünya haqqında çox ziddiyyətli bir şəkildə düşünülür.

Mənim sualım (ümumi)

"Mantıksal" (yəni, "insan ağlı" üçün) olan bir məlumat strukturunu təmsil etmək üçün doğru C # üsulu yalnız bir neçə zəng və ıslıkla işləyən bir things ?

List<T> zaman qəbuledilməzdir? Bu məqbul zaman nədir? Niyə, niyə olmasın? List<T> miras olub-olmamasına qərar verərkən bir proqramçı nəzərə alınmalıdır?

1033
11 февр. Superbest 11 fevralda təyin olundu 2014-02-11 06:01 '14 saat 06:01 'da 2014-02-11 06:01
@ 26 cavab

Burada yaxşı cavablar var. Onlara aşağıdakı nöqtələr əlavə edərdim.

"Mantıksal" (yəni, "insan ağlı" üçün) olan # məlumat strukturunu təmsil etmək üçün doğru yol nədir - yalnız bir neçə zəng və ıslık ilə işlərin siyahısı?

Boşluğu doldurmaq üçün futbolla tanış olan on qeyri-kompüter proqramçıdan soruş:

 A football team is a particular kind of _____ 

Kimsə "bir neçə zəng ilə oyunçuların siyahısı" dedilər mi və ya hamısı "idman komandası" və ya "klub" və ya "təşkilat" deyildilər? Bir futbol komandasının oyunçuların xüsusi bir siyahısı olduğunu düşündüyünüz fikriniz və zehininizdir.

List<T> bir mexanizmdir. Futbol komandası bir iş obyektidir, yəni bir proqramın iş sahəsindəki konsepsiyanı təmsil edən bir obyektdir. Onları qarışdırmayın! Futbol komandası bir növ komandadır; Bir siyahı siyahısı var - oyunçuların siyahısı. Siyahı oyunçuların ayrı bir siyahısı deyil. Siyahı oyunçuların siyahısı. Buna görə, List<Player> olan bir Roster əmlakı yaradın. ReadOnlyList<Player> ilə bunu etsəniz, futbol komandası haqqında bilən hər kəs siyahıdan oyunçuları çıxara bilər.

List<T> zaman qəbuledilməzdir?

Kimin üçün qəbuledilməzdir? Mənə mi? Xeyr

Bu məqbul zaman nədir?

Listenin List<T> mexanizmini genişləndirən bir mexanizm yaratdığınızda.

List<T> miras olub-olmamasına qərar verərkən bir proqramçı nəzərə alınmalıdır?

Bir mexanizm və ya iş obyekti yaradırmı?

Amma bu bir çox kod! İşləyən hər şey üçün nə alacağam?

Sualınıza daxil daha çox vaxt sərf etdik ki, müvafiq Tərtib List<T> üzvləri üçün əlli dəfə transfer metodları yazmalısınız. Siz aydınlıqdan qorxmurdunuz və burada çox az miqdarda koddan danışırıq; bir neçə dəqiqəlik iş var.

ƏLAVƏ OLUNUB

Bir az düşündüm və bir futbol komandasını oyunçuların siyahısı kimi simule etməmək üçün başqa bir səbəb var. Əslində, bir futbol komandasını oyunçuların siyahısı kimi modelləşdirmək yaxşı bir fikirdir. Oyunçuların siyahısı olan bir komandanın problemi, hazırda komandanın bir anlıq şəklində olmasıdır. İşinizin bu sinif üçün nə olduğunu bilmirdim, amma futbol komandasını təmsil edən bir sinif varsa, ona suallar vermək istərdim: "2003-cü ildən 2013-cü ilə qədər yaralanma səbəbiylə neçə Seahawks oyunçusu oyundan qaçırdı?" ya da daha əvvəl başqa komandada oynamış Denverdəki oyunçu, həyətlərdəki illər ərzində ən çox artım göstərdi? " və ya " Pigers bu il bütün yol getdi mi?

Yəni, futbol komandası tarixi faktların toplusu kimi yaxşı modelləşdirilmişdir, məsələn, oyunçu işə qəbul edilərkən, zədələnmiş, təqaüdçü və s. Aydındır ki, mövcud oyunçuların siyahısı, ehtimal ki, mərkəzin qabağında olması vacib bir faktdır, ancaq daha çox tarixi bir perspektiv tələb edən bu obyekt ilə əlaqəsi olan başqa maraqlı şeylər ola bilər.

1196
11 февр. Cavab 11 fevralda Eric Lippert tərəfindən verilir . 2014-02-11 08:43 '14 at 8:43 2014-02-11 08:43

Nəhayət, bəzi təklifləri siyahıda bir şeylə örtmək təklifi var:

Bu doğru yol. "Şübhəsiz Verbosity" ona baxmaq üçün pis bir yoludur. my_team.Players.Count açıq bir məna var. Oyunçular saymaq istəyirsən.

 my_team.Count 

.. heç bir şey demək deyil. Nə düşünürsən?

Komandanın yalnız oyunçuların siyahısından ibarət olmayan bir siyahı deyil. Komanda oyunçulara sahibdir, buna görə oyunçuların bir qismi olmalıdır (üzv).

Bu, həqiqətən, çox vacibdir ki, həqiqətən narahat olursanız, həmişə əmrləri əmrdən təyin edə bilərsiniz:

border=0
 public int PlayerCount { get { return Players.Count; } } 

.. olur:

 my_team.PlayerCount 

İndi Demeter Qanunu mənasını verir.

Kompozit təkrar istifadə prinsipini də nəzərə almalıyıq. List<T> köçürdükdə, bir komandanın bir oyunçu olduğunu və ondan lazımsız metodlar ortaya qoyduğunu söyləyirsiniz. Bu səhvdir. Dediyiniz kimi, komanda oyunçuların siyahısından çoxdur: adı, idarəçiləri, idarə heyəti üzvləri, məşqçiləri, tibb işçiləri, əmək haqqı və s. Komandanızın sinifində oyunçuların siyahısı varsa, "Komanda oyunçuların siyahısı var", ancaq başqa şeylər də ola bilər.

242
11 февр. Cavab Simon Whitehead tərəfindən 11 fevralda verilir . 2014-02-11 06:05 '14 saat 06:05 'da 2014-02-11 06:05

Vay, postunuzda bir çox sual və xal var. Microsoft-dan əldə etdiyiniz arqumentlərin əksəriyyəti sağdır. List<T> haqqında hər şeylə başlayın

  • List<T> yüksək səviyyədə optimallaşdırıldı. Onun əsas istifadə obyektin xüsusi üzvü kimi istifadə olunmalıdır.
  • Microsoft bunu möhürlədi, çünki bəzən daha rahat bir ad olan bir sinif yaratmaq lazım ola bilər: class MyList<T, TX>: List<CustomObject<T, Something<TX>> {... } . İndi var list = new MyList<int, string>(); kimi sadədir var list = new MyList<int, string>(); ,
  • CA1002: ümumi siyahıları ifşa etmirsiniz: prinsipcə, bu proqramı yeganə bir geliştirici kimi istifadə etməyi planlaşdırırsınızsa belə yaxşı kodlaşdırma təcrübəsi ilə inkişaf etdirməyə dəyər, belə ki, onlar sizə və ikinci təbiətə daxil olurlar. Əgər indeks siyahısına sahib olmaq üçün bir istehlakçıya ehtiyacınız varsa, siyahı hələ IList<T> kimi göstərə bilərsiniz. Bu, daha sonra sinifdə tətbiqin dəyişdirilməsini təmin edəcək.
  • Microsoft Collection<T> çox ümumi etdi, çünki ümumi bir konsepsiya ... adı bütün bunları söyləyir; bu yalnız bir kolleksiya. SortedCollection<T> , ObservableCollection<T> , ReadOnlyCollection<T> və s. Kimi daha dəqiq versiyaları var, hər biri IList<T> yerinə IList<T> lakin List<T> .
  • Collection<T> üzvlərə (yəni, əlavə, silmək, və s.) Virtual olduğu üçün köhnəlməsinə imkan verir. List<T> - yox.
  • Sualınızın son hissəsi yerindədir. Futbol komandası yalnız oyunçuların siyahısı deyil, belə ki, bu oyunçuların siyahısını ehtiva edən bir sinif olmalıdır. Kompozisiyaya qarşı mirasçılığı düşünün. Futbol komandası oyunçuların siyahısı (siyahı) var, bu oyunçuların siyahısı deyil.

Bu kodu yazsaydım, sinif yəqin ki, belə bir şeyə bənzəyir:

 public class FootballTeam { // Football team rosters are generally 53 total players. private readonly List<T> _roster = new List<T>(53); public IList<T> Roster { get { return _roster; } } // Yes. I used LINQ here. This is so I don't have to worry about // _roster.Length vs _roster.Count vs anything else. public int PlayerCount { get { return _roster.Count(); } } // Any additional members you want to expose/wrap. } 
118
11 февр. Cavab 11 fevralda verilir . 2014-02-11 06:26 '14 saat 06:26 'da 2014-02-11 06:26
 class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal; } 

Əvvəlki kod deməkdir: küçə futbol oynayan futbolçuların bir dəstəsi və bir adı var görünür. Kimi bir şey:

2019

105
11 февр. Nean Der Thal tərəfindən 11 Fevralda verilən cavab . 2014-02-11 18:45 '14 saat 18:45 'da 2014-02-11 18:45

Bu kompozisiya vs mirasın klassik nümunəsidir.

Bu xüsusi hallarda:

Komanda əlavə davranışa sahib oyunçuların siyahısı.

və ya

Komanda oyunçuların siyahısını ehtiva edən öz obyekti.

Siyahı genişləndirilərkən, özünüzü bir neçə yolla məhdudlaşdırırsınız:

  • Erişimi məhdudlaşdıra bilməzsiniz (məsələn, siyahı dəyişən insanları dayandırın). İstədiyiniz / istədiyiniz hər şeyi istəməyinizi istər-istəməyiniz bütün List üsullarını əldə edirsiniz.

  • Başqa şeylər siyahıları olmaq istəyirsinizsə nə olur. Məsələn, komandalar məşqçiləri, menecerləri, pərəstişkarları, avadanlıqları və s. Onların bəziləri özləri də siyahılar ola bilər.

  • Miras üçün seçiminizi məhdudlaşdırırsınız. Məsələn, ümumi bir Team obyekti yarada və BaseballTeam, FootballTeam və s. Əldə edə bilərsiniz. Siyahıdan devralmaq üçün, Komandanın devralması lazımdır, ancaq bu fərqli qrupların bütün qrupları bu siyahıdakı eyni tətbiqə məcbur olmaq deməkdir.

Kompozisiya - obyektinizdə istədiyiniz davranışı verən obyekt daxildir.

Miras - Sizin obyekt istədiyiniz davranışı olan bir obyektin nümunəsi olur.

Hər ikisinin də istifadəsi var, ancaq bu kompozisiya üstünlük təşkil edəndə aydın olur.

84
11 февр. Cavab 11 fevral tarixində Tim B tərəfindən verilmişdir . 2014-02-11 13:58 '14 at 13:58 2014-02-11 13:58

Hər kəs qeyd etdiyimiz kimi, oyunçuların komandası oyunçuların siyahısı deyil. Bu səhv dünyanın müxtəlif yerlərində, eləcə də müxtəlif səviyyələrdə biliklərdən ibarətdir. Tez-tez problem bu vəziyyətdə olduğu kimi incə və bəzən çox kobud olur. Belə tikililər pisdir, çünki onlar Liskovun dəyişdirilməsi prinsipini pozurlar. Bu konsepsiyanı izah edən internetdə çox yaxşı məqalələr var, məsələn http://en.wikipedia.org/wiki/Liskov_substitution_principle

Beləliklə, valideynlər və uşaqlar arasında siniflər arasındakı əlaqədə saxlamaq üçün lazım olan iki qaydalar vardır:

  • Uşaq hər hansı bir xüsusiyyətə malik olmamalıdır, bu, valideynləri tamamilə müəyyənləşdirmədən azdır.
  • Bir valideynin uşağını tamamilə müəyyən edənə əlavə bir xüsusiyyətə sahib olmaması lazımdır.

Başqa sözlə, valideyn uşaq üçün zəruri bir tərifdir və uşağın valideynin kifayət qədər bir təsviri var.

Burada bir həll yolu ilə düşünmək və yuxarıda göstərilən prinsipi tətbiq etmək, belə bir səhvdən qaçınmaq üçün bir yol. Ana sinifdə olan bütün əməliyyatlar, həm də struktur və semantik olaraq törəmə sinif üçün etibarlı olub olmadığını yoxlamaqla hipotezi sınamaq lazımdır.

  • Futbol komandasında futbolçuların siyahısı varmı? (Siyahının bütün xüsusiyyətləri eyni dəyərdə olan əmrə aiddir)
    • Bir sıra homojen obyektlərin toplusudurmu? Bəli, komanda oyunçuların toplusudur.
    • Oyunçuların komanda statüsünün təsvirinə daxil edilməsi və komandanın ardıcıllığın heç bir açıq dəyişiklik olmadan qorunmasını təmin etməsi əmridirmi? Xeyr və yox
    • Oyunçuların komandada tutarlı mövqeyinə əsasən açılması / qapalı olması gözlənilirmi? Xeyr

Gördüyünüz kimi, siyahıdakı ilk xüsusiyyət yalnız əmrə aiddir. Buna görə də əmr bir siyahı deyil. Siyahı komandanızın necə idarə olunacağına dair tətbiq olunan təfsilat olacaqdır, belə ki, yalnız oyunçu obyektlərini saxlamaq və komanda sinifinin metodlarından istifadə edərək onları idarə etmək üçün istifadə olunmalıdır.

Bu nöqtədə qeyd etmək istərdim ki, Team sinifi, mənim fikrimcə, Siyahıdan istifadə edilməməlidir; əksər hallarda Set Data quruluşu (məsələn, HashSet) istifadə edilməlidir.

46
11 февр. cavab 11 fevralda Satyan Raina tərəfindən verilir. 2014-02-11 19:56 '14 da 19:56 2014-02-11 19:56

FootballTeam komandası əsas komanda ilə birlikdə ehtiyat qrupuna malikdirsə?

 class FootballTeam { List<FootballPlayer> Players { get; set; } List<FootballPlayer> ReservePlayers { get; set; } } 

Necə modelləşdirirsiniz?

 class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal } 

Əlaqələr açıq bir şəkildə var və deyil.

və ya RetiredPlayers ?

 class FootballTeam { List<FootballPlayer> Players { get; set; } List<FootballPlayer> ReservePlayers { get; set; } List<FootballPlayer> RetiredPlayers { get; set; } } 

Bir kolleksiyanı devralmaq istəyirsənsə, Tipik olaraq Class SomethingCollection adını SomethingCollection .

Sizin SomethingCollection semantically mənası deməkdir? Yalnız sizin növü bir Something müəyyən əgər bunu.

FootballTeam halda FootballTeam bu səhv səslənir. Bir Team bir Collection çoxdur. Team digər cavablarla göstərildiyi kimi məşqçiləri, məşqçiləri və s. Ola bilər.

FootballCollection futbol topları və ya bəlkə futbol aksesuarlarının toplusu kimi səslənir. TeamCollection , komandaların bir kolleksiyası.

FootballPlayerCollection , həqiqətən, istəyərsinizsə, List<FootballPlayer> miras qalan bir sinif üçün etibarlı bir ad olan bir oyunçu kolleksiyası kimi səslənir.

Həqiqətən, List<FootballPlayer> olduqca yaxşı bir növüdür. Bəlkə IList<FootballPlayer> bir üsuldan qaytarırsan.

Nəticədə

Özünüzə soruşun

  • X bir Y ? ya da X Y ?

  • Sınıfımdakı adlar nədir?

39
12 февр. bu cavab fevralın 12-də Sam Leach tərəfindən verilir . 2014-02-12 14:41 '14 at 2:41 pm 2014-02-12 14:41

Hər şeydən əvvəl, bu, məqbuldur. Vərəsəliyin istifadə edildiyi təqdirdə, Team sinfi yalnız obyektlərin manipulyasiyası üçün nəzərdə tutulan davranışları (üsulları) ifşa edəcəkdir. Məsələn, AsReadOnly() və ya CopyTo(obj) metodları bir komanda obyekti üçün CopyTo(obj) . AddRange(items) metodunun əvəzinə, daha çox təsviri AddPlayers(players) üsuluna ehtiyacınız olacaq.

LINQ istifadə etmək istəyirsinizsə, ICollection<T> və ya IEnumerable<T> kimi universal bir interfeysdən istifadə etmək daha mənalı olacaq.

Artıq qeyd olunduğu kimi, kompozisiya düzgün yoludur. Sadəcə oyunçuların siyahısını xüsusi dəyişən kimi tətbiq edin.

29
11 февр. Dmitri S. tərəfindən verilmiş cavab 11 fevral 2014-02-11 06:25 '14 saat 06:25 AM 2014-02-11 06:25

Layihə> Tətbiq

Təsvir etdiyiniz metod və xüsusiyyətlər hansılardır? Hansı baz sinifini devraldığınız bir tərifdir. Birinci addım geri çəkilməyə dəyər olduğunu düşünürəm.

Bir obyekt məlumat və davranışların toplusudur.

Beləliklə, ilk suallarınız olmalıdır:

  • Bu obyektin yaratdığım modeldə hansı məlumatlar var?
  • Bu obyekt bu modeldə hansı davranışı göstərir?
  • Gələcəkdə bu necə dəyişə bilər?

Mirasın isa (a) əlaqəsini nəzərdə tutduğunu nəzərə alsaq, kompozisiyanın varlığı (əlaqələri) olduğunu bildirir. Təqdimatınızdakı vəziyyətin dəyişə biləcəyini nəzərə alaraq, təqdimatınızdakı vəziyyətiniz üçün düzgün seçim edin.

Bəzi insanlar beynini "dizayn rejimində" yerləşdirmək daha asan olduğu üçün, müəyyən tiplər barədə düşünmədən əvvəl interfeyslərdə düşünməyi düşünün.

Gündəlik kodlaşdırmada hər kəs bu səviyyədə bilinçli deyil. Ancaq belə bir mövzunu düşünsəniz, layihənin sularını döyün. Bunu bilməli bir azad ola bilər.

Dizayn spesifikasiyasını nəzərdən keçirin.

MSDN və ya Visual Studio haqqında <T> və IList <T> siyahısına baxın. Onlar ortaya qoyduqları üsullara və xüsusiyyətlərə baxın. Sizin fikrinizcə FootballTeam ilə birləşmək istəməyən bütün bu üsullara sahibsinizmi?

FootballTeam.Reverse () sizin üçün mantıksız mı? Будет ли footballTeam.ConvertAll <TOutput> () выглядеть так, как вы хотите?

Это не трюк; ответ может быть действительно "да". Если вы реализуете/наследуете List <Player> или IList <Player> , вы застряли с ними; если этот идеал для вашей модели, сделайте это.

Если вы решите "да", это имеет смысл, и вы хотите, чтобы ваш объект обрабатывался как коллекция/список игроков (поведение), и поэтому вы хотите реализовать ICollection или IList, во что бы то ни стало. Номинально:

 class FootballTeam : ... ICollection<Player> { ... } 

Если вы хотите, чтобы ваш объект содержал коллекцию/список игроков (данных), и поэтому вы хотите, чтобы коллекция или список являлись свойством или членом, обязательно сделайте это. Номинально:

 class FootballTeam ... { public ICollection<Player> Players { get { ... } } } 

Вы можете почувствовать, что хотите, чтобы люди могли перечислять только список игроков, а не считать их, добавлять к ним или удалять их. IEnumerable < Игрок > является вполне допустимым вариантом для рассмотрения.

Вы можете почувствовать, что ни один из этих интерфейсов не является полезным в вашей модели вообще. Это менее вероятно (IEnumerable <T> полезен во многих ситуациях), но это все еще возможно.

Любой, кто пытается сказать вам, что один из них является категорически и окончательно неправильным , в каждом случае ошибочен. Любой, кто пытается сказать вам это категорически и окончательно right , в каждом случае ошибочен.

Переход к реализации

Как только вы решите данные и поведение, вы можете принять решение о реализации. Это включает в себя, какие конкретные классы вы будете зависеть от наследования или композиции.

Это не может быть большим шагом, и люди часто объединяют дизайн и реализацию, поскольку вполне возможно пропустить все это в вашей голове через секунду или два и начать печатать.

Эксперимент с мыслями

Искусственный пример: как отмечали другие, команда не всегда является "просто" коллекцией игроков. Поддерживаете ли вы коллекцию партитур для команды? Является ли команда взаимозаменяема с клубом, в вашей модели? Если это так, и если ваша команда является коллекцией игроков, возможно, она также является коллекцией персонала и/или коллекцией баллов. Затем вы получите:

 class FootballTeam : ... ICollection<Player>, ICollection<StaffMember>, ICollection<Score> { .... } 

Несмотря на это, в этот момент на С# вы не сможете реализовать все это, наследуя List <T> так или иначе, поскольку С# "only" поддерживает одиночное наследование. (Если вы пробовали этот malarky в С++, вы можете считать это хорошим делом.) Реализация одной коллекции через наследование, а другая через композицию, вероятно, будет грязной. И такие свойства, как Count, становятся запутанными для пользователей, если вы явно не реализуете ILIst <Player> .Count и IList <StaffMember> .Count и т.д., А затем они просто болезненны, а не запутывают. Вы можете видеть, где это происходит; но в то время как мышление по этому проспекту вполне может сказать вам, что ему нехорошо идти в этом направлении (и правильно или неправильно, ваши коллеги могут также, если бы вы реализовали его таким образом!)

Короткий ответ (слишком поздно)

Руководство по не наследованию от классов коллекции не является специфичным для С#, вы найдете его на многих языках программирования. Получается мудрость, а не закон. Одна из причин заключается в том, что на практике считается, что композиция часто выигрывает над наследованием с точки зрения понятности, реализуемости и ремонтопригодности. Это более распространено с объектами реального мира/домена, чтобы находить полезные и последовательные отношения "hasa", чем полезные и последовательные отношения "isa", если вы не глубоки в абстрактном, особенно с течением времени и точными данными и поведением объектов в коде изменения. Это не должно заставлять вас всегда исключать наследование из классов коллекции; но это может быть наводящим на размышления.

27
ответ дан El Zorko 12 февр. '14 в 1:27 2014-02-12 01:27

Футбольная команда не - список футболистов. Футбольная команда состоит из списка футболистов!

Это логически неверно:

 class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal } 

и это верно:

 class FootballTeam { public List<FootballPlayer> players public string TeamName; public int RunningTotal } 
24
ответ дан sam 11 февр. '14 в 19:25 2014-02-11 19:25

Это зависит от контекста

Когда вы рассматриваете свою команду как список игроков, вы проецируете "идею" команды шара ноги на один аспект: вы уменьшаете "команду" до людей, которых вы видите на поле. Эта проекция верна только в определенном контексте. В другом контексте это может быть совершенно неправильно. Представьте, что вы хотите стать спонсором команды. Поэтому вам нужно поговорить с менеджерами команды. В этом контексте команда прогнозируется в списке своих менеджеров. И эти два списка обычно не перекрываются очень сильно. Другими контекстами являются текущие и прежние игроки и т.д.

Нечеткая семантика

Таким образом, проблема с учетом команды в качестве списка ее игроков заключается в том, что ее семантика зависит от контекста и что она не может быть расширена при изменении контекста. Кроме того, трудно выразить, какой контекст вы используете.

Классы расширяемы

Когда вы используете класс с одним членом (например, IList activePlayers ), вы можете использовать имя члена (и, кроме того, его комментарий), чтобы сделать контекст понятным. Когда есть дополнительные контексты, вы просто добавляете дополнительный элемент.

Классы сложнее

В некоторых случаях может возникнуть избыток для создания дополнительного класса. Каждое определение класса должно быть загружено через загрузчик классов и будет кэшироваться виртуальной машиной. Это требует производительности и памяти во время выполнения. Когда у вас есть очень специфический контекст, может быть, стоит подумать о футбольной команде как о списке игроков. Но в этом случае вы должны просто использовать IList , а не класс, полученный из него.

Заключение/соображения

Когда у вас есть очень специфический контекст, вполне нормально рассматривать команду как список игроков. Например, внутри метода вполне нормально писать

 IList<Player> footballTeam = ... 

При использовании F # может даже быть ОК, чтобы создать сокращение типа

 type FootballTeam = IList<Player> 

Но когда контекст шире или даже неясен, вы не должны этого делать. Это особенно важно, когда вы создаете новый класс, где неясно, в каком контексте он может использоваться в будущем. Предупреждающий знак - это когда вы начинаете добавлять дополнительные атрибуты в свой класс (имя команды, тренера и т.д.). Это явный признак того, что контекст, в котором будет использоваться класс, не является фиксированным и изменится в будущем. В этом случае вы не можете рассматривать команду как список игроков, но вы должны моделировать список игроков (в настоящее время активных, а не травмированных и т.д.) В качестве атрибута команды.

20
ответ дан stefan.schwetschke 11 февр. '14 в 13:10 2014-02-11 13:10

Позвольте мне переписать ваш вопрос. поэтому вы можете увидеть тему с другой точки зрения.

Когда мне нужно представлять футбольную команду, я понимаю, что это в основном имя. Например: "The Eagles"

 string team = new string(); 

Затем я понял, что у команд также есть игроки.

Почему я не могу просто расширить тип строки, чтобы он также содержал список игроков?

Ваша точка входа в проблему произвольна. Попытайтесь подумать, что имеет команда (свойства), а не то, что она .

После этого вы можете увидеть, разделяет ли он свойства с другими классами. И подумайте о наследовании.

19
ответ дан user1852503 16 февр. '14 в 10:56 2014-02-16 10:56

Просто потому, что я думаю, что другие ответы в значительной степени уходят по тангенсу, является ли футбольная команда "is-a" List<FootballPlayer> или "has-a" List<FootballPlayer> , которая на самом деле не отвечает на этот вопрос, как написано.

ОП в основном просит уточнить руководящие принципы для наследования от List<T> :

В руководстве говорится, что вы не должны наследовать от List<T> . Niyə olmasın?

Потому что List<T> не имеет виртуальных методов. Это меньше проблем в вашем собственном коде, так как вы обычно можете отключить реализацию с относительно небольшой болью - но это может быть гораздо больше в публичном API.

Что такое публичный API и почему меня это беспокоит?

Открытый API - это интерфейс, который вы предоставляете сторонним программистам. Подумайте о каркасе. И напомним, что ссылки, на которые ссылаются, - это "Рекомендации по разработке .NET Framework", а не "Рекомендации по разработке приложений .NET". Есть разница, и, вообще говоря, публичный дизайн API намного более строгий.

Если мой текущий проект не имеет и вряд ли когда-либо будет иметь этот открытый API, могу ли я смело игнорировать это руководство? Если я наследую List и получается, мне нужен публичный API, какие трудности у меня есть?

Довольно много, да. Возможно, вам стоит подумать об обоснованности этого вопроса, чтобы убедиться, что оно применимо к вашей ситуации в любом случае, но если вы не создаете публичный API, вам не нужно беспокоиться об особенностях API, таких как управление версиями (из которых это подмножество).

Если вы добавите публичный API в будущем, вам придется либо абстрагировать ваш API от вашей реализации (не подвергая непосредственно List<T> ), либо нарушать рекомендации с возможной будущей болью, которая влечет за собой.

Почему это даже имеет значение? Список - это список. Что может измениться? Что я могу изменить?

Зависит от контекста, но поскольку мы используем FootballTeam в качестве примера, представьте, что вы не можете добавить FootballPlayer , если это приведет к тому, что команда перейдет на ограничение зарплаты. Возможным способом добавления этого будет что-то вроде:

  class FootballTeam : List<FootballPlayer> { override void Add(FootballPlayer player) { if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) { throw new InvalidOperationException("Would exceed salary cap!"); } } } 

А... но вы не можете override Add , потому что это не virtual (по соображениям производительности).

Если вы находитесь в приложении (что в основном означает, что вы и все ваши собеседники скомпилированы вместе), вы можете теперь изменить на IList<T> и исправить любые ошибки компиляции:

  class FootballTeam : IList<FootballPlayer> { private List<FootballPlayer> Players { get; set; } override void Add(FootballPlayer player) { if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) { throw new InvalidOperationException("Would exceed salary cap!"); } }  } 

но если вы публично выступили с третьим лицом, вы только что внесли взломанное изменение, которое вызовет ошибки компиляции и/или времени выполнения.

Tl; DR - рекомендации для общедоступных API. Для частных API выполните то, что вы хотите.

15
ответ дан Mark Brackett 07 апр. '15 в 0:35 2015-04-07 00:35

Позволяет ли людям говорить

 myTeam.subList(3, 5); 

имеет смысл вообще? Если нет, то это не должно быть списком.

14
ответ дан Cruncher 12 февр. '14 в 0:42 2014-02-12 00:42

Это зависит от поведения вашего "командного" объекта. Если он ведет себя точно так же, как в коллекции, возможно, это будет нормально представлять его сначала с помощью простого списка. Тогда вы можете начать замечать, что вы продолжаете дублировать код, который повторяется в списке; на данный момент у вас есть возможность создать объект FootballTeam, который обертывает список игроков. Класс FootballTeam становится домом для всего кода, который повторяется в списке игроков.

Это делает мой код излишне подробным. Теперь я должен назвать my_team.Players.Count вместо my_team.Count. К счастью, с С# я могу определить индексаторов, чтобы сделать индексирование прозрачным, и переслать все методы внутреннего списка... Но это много кода! Что я получу за все, что работает?

Герметизация. Ваши клиенты не должны знать, что происходит внутри FootballTeam. Для всех ваших клиентов это может быть реализовано путем просмотра списка игроков в базе данных. Им не нужно знать, и это улучшает ваш дизайн.

Это просто не имеет никакого смысла. Футбольная команда не имеет "списка" игроков. Это список игроков. Вы не говорите: "Джон Макфуталлер присоединился к игрокам SomeTeam". Вы говорите: "Джон присоединился к SomeTeam". Вы не добавляете письмо к "строковым символам", вы добавляете письмо к строке. Вы не добавляете книгу в библиотечные книги, вы добавляете книгу в библиотеку.

Точно:) вы скажете footballTeam.Add(john), а не footballTeam.List.Add(john). Внутренний список не будет виден.

13
ответ дан xpmatteo 11 февр. '14 в 19:51 2014-02-11 19:51

Что такое правильный способ С# для представления структуры данных...

Помните: "Все модели ошибочны, но некоторые из них полезны". - Джордж Е. П. Бокс

Нет "правильного пути", только полезного.

Выберите тот, который вам полезен и/ваши пользователи. Odur. Развивайте экономически, не переусердствуйте. Чем меньше кода вы пишете, тем меньше кода вам нужно будет отлаживать. (читайте следующие выпуски).

- Отредактировано

Мой лучший ответ будет... это зависит. Наследование из списка приведет к тому, что клиенты этого класса будут подвергнуты методам, которые могут не отображаться, прежде всего потому, что FootballTeam выглядит как бизнес-объект.

- Издание 2

Я искренне не помню, что я имел в виду в комментарии "не переучивайтесь". Хотя я считаю, что KISS мышление - хорошее руководство, я хочу подчеркнуть, что наследование бизнес-класса из списка создаст больше проблем, чем устраняет, из-за утечка абстракции .

С другой стороны, я считаю, что существует ограниченное число случаев, когда просто наследовать из Списка полезно. Как я писал в предыдущем издании, все зависит. Ответ на каждый случай сильно зависит от знания, опыта и личных предпочтений.

Спасибо @kai за то, что помогли мне более точно подумать о ответе.

11
ответ дан Arturo Tena 11 февр. '14 в 21:03 2014-02-11 21:03

Здесь есть много отличных @, но я хочу затронуть то, о чем я не упоминал: Объектно-ориентированный дизайн - это расширение прав и возможностей объектов .

Вы хотите инкапсулировать все свои правила, дополнительную работу и внутренние детали внутри соответствующего объекта. Таким образом, другим объектам, взаимодействующим с этим, не нужно беспокоиться обо всем этом. На самом деле, вы хотите сделать шаг дальше и активно предотвращать обход других объектов внутри этих объектов.

Когда вы наследуете от List , все остальные объекты могут видеть вас как Список. Они имеют прямой доступ к методам добавления и удаления игроков. И ты потеряешь контроль; məsələn:

Предположим, вы хотите различать, когда игрок уходит, зная, удалены ли они, ушли или уволены. Вы можете реализовать метод RemovePlayer , который принимает соответствующее перечисление ввода. Однако, наследуя от List , вы не сможете предотвратить прямой доступ к Remove , RemoveAll и даже Clear . В результате у вас действительно disempowered ваш класс FootballTeam .


Дополнительные мысли об инкапсуляции... Вы подняли следующую озабоченность:

Это делает мой код излишне подробным. Теперь я должен вызывать my_team.Players.Count, а не только my_team.Count.

Вы правы, это было бы излишне подробным для всех клиентов, чтобы использовать вашу команду. Однако эта проблема очень мала по сравнению с тем фактом, что вы подвергли List Players всем и каждому, чтобы они могли играть с вашей командой без вашего согласия.

Вы продолжаете говорить:

Это просто не имеет никакого смысла. Футбольная команда не имеет "списка" игроков. Это список игроков. Вы не говорите: "Джон Макфуталлер присоединился к игрокам SomeTeam". Вы говорите: "Джон присоединился к SomeTeam".

Вы ошибаетесь в первом бит: оставьте слово "список", и на самом деле очевидно, что в команде есть игроки.
Тем не менее, вы ударили ноготь по голове вторым. Вы не хотите, чтобы клиенты вызывали ateam.Players.Add(...) . Вы хотите, чтобы они вызывали ateam.AddPlayer(...) . И ваша реализация (возможно, между прочим) вызовет Players.Add(...) внутренне.


Надеюсь, вы увидите, насколько важна инкапсуляция в целях расширения возможностей ваших объектов. Вы хотите, чтобы каждый класс хорошо выполнял свою работу, не опасаясь вмешательства со стороны других объектов.

11
ответ дан Disillusioned 15 февр. '14 в 23:05 2014-02-15 23:05

Это напоминает мне о том, что "есть" против "имеет" компромисс. Иногда это проще и имеет смысл наследовать непосредственно из суперкласса. В других случаях имеет смысл создать отдельный класс и включить класс, который вы унаследовали бы от переменной-члена. Вы по-прежнему можете получить доступ к функциям класса, но не привязаны к интерфейсу или каким-либо другим ограничениям, которые могут возникнуть в результате наследования класса.

Что вы делаете? Как и во многих вещах... это зависит от контекста. Руководство, которое я хотел бы использовать, состоит в том, что для наследования другого класса действительно должно быть отношение "есть". Так что, если вы пишете класс под названием BMW, он может унаследовать автомобиль, потому что BMW поистине автомобиль. Класс лошади может наследовать от класса млекопитающих, потому что лошадь на самом деле является млекопитающим в реальной жизни, и любая функциональность Млекопитающих должна относиться к Лошади. Но можете ли вы сказать, что команда - это список? Из того, что я могу сказать, это не похоже, что команда действительно "является" списком. Поэтому в этом случае у меня был бы List как переменная-член.

10
ответ дан Paul J Abernathy 11 февр. '14 в 17:56 2014-02-11 17:56

Как говорится в руководстве, публичный API не должен раскрывать внутреннее дизайнерское решение о том, используете ли вы список, набор, словарь, дерево или что-то еще. "Команда" не обязательно является списком. Вы можете реализовать его как список, но пользователи вашего общедоступного API должны использовать ваш класс в необходимости знать основы. Это позволяет вам изменить свое решение и использовать другую структуру данных, не затрагивая публичный интерфейс.

8
ответ дан QuentinUK 18 февр. '14 в 5:53 2014-02-18 05:53

Мой грязный секрет: мне все равно, что говорят люди, и я это делаю..NET Framework распространяется с помощью "XxxxCollection" (UIElementCollection для примера моей вершины).

Итак, что мешает мне сказать:

 team.Players.ByName("Nicolas") 

Когда я нахожу это лучше, чем

 team.ByName("Nicolas") 

Кроме того, мой PlayerCollection может использоваться другим классом, например "Club", без дублирования кода.

 club.Players.ByName("Nicolas") 

Лучшие практики вчерашнего дня, возможно, не будут завтра. Нет оснований для большинства лучших практик, большинство из них являются лишь широким соглашением между сообществом. Вместо того, чтобы спрашивать сообщество, если он обвинит вас в том, что вы спросите себя, что является более читаемым и поддерживаемым?

 team.Players.ByName("Nicolas") 

və ya

 team.ByName("Nicolas") 

Həqiqətən. У вас есть какие-то сомнения? Теперь, возможно, вам нужно играть с другими техническими ограничениями, которые не позволяют использовать List <T> в вашем реальном прецеденте. Но не добавляйте ограничение, которое не должно существовать. Если Microsoft не документировала, почему, то это, безусловно, "лучшая практика", которая приходит из ниоткуда.

8
ответ дан Nicolas Dorier 12 февр. '14 в 16:57 2014-02-12 16:57

Когда они говорят, что List<T> "оптимизирован", я думаю, они хотят иметь в виду, что у него нет таких функций, как виртуальные методы, которые стоят дороже. Таким образом, проблема заключается в том, что после того, как вы раскрываете List<T> в общедоступном API , вы теряете способность применять бизнес-правила или настраивать свои функции позже. Но если вы используете этот унаследованный класс как внутренний в своем проекте (в отличие от потенциально подверженного тысячам ваших клиентов/партнеров/других команд как API), тогда это может быть ОК, если это экономит ваше время, и это функциональность, которую вы хотите дублировать. Преимущество наследования от List<T> заключается в том, что вы устраняете много немого кода обертки, который никогда не будет настроен в обозримом будущем. Кроме того, если вы хотите, чтобы ваш класс явно имел ту же семантику, что и List<T> для жизни ваших API, также может быть и ОК.

Я часто вижу, что многие люди делают тонны дополнительной работы только из-за правила FxCop, так или иначе, что блог говорит, что это "плохая" практика. Много раз, это превращает код в дизайн палодовости паттерн. Как и в случае с множеством рекомендаций, относитесь к нему как к руководству, которое может иметь исключения.

5
ответ дан ShitalShah 16 февр. '14 в 10:50 2014-02-16 10:50

Хотя у меня нет сложного сравнения, поскольку большинство из этих @, я хотел бы поделиться своим методом для обработки этой ситуации. Расширяя IEnumerable<T> , вы можете позволить вашему классу Team поддерживать расширения запросов Linq, не публично раскрывая все методы и свойства List<T> .

 class Team : IEnumerable<Player> { private readonly List<Player> playerList; public Team() { playerList = new List<Player>(); } public Enumerator GetEnumerator() { return playerList.GetEnumerator(); } ... } class Player { ... } 
3
ответ дан Null511 20 янв. '18 в 5:43 2018-01-20 05:43

Я просто хотел добавить, что Бертран Мейер, изобретатель Эйфеля и дизайн по контракту, имел бы Team наследовать от List<Player> , не так как моргнув веком.

В своей книге "Объектно-ориентированное программирование" он обсуждает реализацию системы графического интерфейса, в которой прямоугольные окна могут иметь дочерние окна. Он просто имеет Window наследует как от Rectangle , так и Tree<Window> , чтобы повторно использовать реализацию.

Однако С# не является Eiffel. Последний поддерживает множественное наследование и переименование функций. В С#, когда вы выполняете подкласс, вы наследуете интерфейс и реализацию. Вы можете переопределить реализацию, но соглашения о вызовах копируются непосредственно из суперкласса. Однако в Eiffel вы можете изменить имена общедоступных методов, поэтому вы можете переименовать Add и Remove в Hire и Fire в Team . Если экземпляр Team возвращается обратно к List<Player> , вызывающий использует Add и Remove , чтобы изменить его, но будут вызываться ваши виртуальные методы Hire и Fire .

3
ответ дан Alexey 27 апр. '15 в 14:35 2015-04-27 14:35

Если вашим пользователям класса необходимы все методы и свойства ** Список, вы должны извлечь из него свой класс. Если они им не нужны, приложите список и создайте обертки для методов, которые действительно нужны пользователям класса.

Это строгое правило, если вы пишете публичный API или любой другой код, который будет использоваться многими людьми. Вы можете игнорировать это правило, если у вас есть крошечное приложение и не более 2 разработчиков. Это сэкономит вам время.

Для маленьких приложений вы также можете выбрать другой, менее строгий язык. Ruby, JavaScript - все, что позволяет вам писать меньше кода.

3
ответ дан Ivan Nikitin 16 февр. '14 в 10:59 2014-02-16 10:59

Предпочитают интерфейсы над классами

Классы должны избегать получения классов и вместо этого применять минимальные интерфейсы.

Наследование прерывает инкапсуляцию

Вывод из классов прерывает инкапсуляцию :

  • раскрывает внутренние детали о том, как ваша коллекция реализована
  • объявляет интерфейс (набор публичных функций и свойств), который может быть неприемлем

Помимо прочего, это затрудняет реорганизацию вашего кода.

Классы являются частью реализации

Классы - это детали реализации, которые должны быть скрыты от других частей вашего кода. Короче говоря, System.List представляет собой конкретную реализацию абстрактного типа данных, который может быть или не быть подходящим сейчас и в будущем.

Концептуально тот факт, что тип данных System.List называется "список", немного красно-селедка. System.List<T> - это изменяемая упорядоченная коллекция, которая поддерживает амортизированные операции O (1) для добавления, вставки и удаления элементов и операций O (1) для извлечения числа элементов или получения и установки элемента по индексу.

Чем меньше интерфейс, тем гибче код

При проектировании структуры данных, чем проще интерфейс, тем более гибким является код. Посмотрите, как мощный LINQ для демонстрации этого.

Как выбрать интерфейсы

Когда вы думаете "список", вы должны начать с того, чтобы сказать себе: "Мне нужно представлять коллекцию бейсболистов". Итак, скажем, вы решили смоделировать это с помощью класса. Сначала вы должны решить, какое минимальное количество интерфейсов, которое этот класс должен будет предъявить.

Некоторые вопросы, которые могут помочь в этом процессе:

  • Нужно ли иметь счет? Если не учитывать внедрение IEnumerable<T>
  • Будет ли эта коллекция изменяться после ее инициализации? Если не считать IReadonlyList<T> .
  • Важно ли, чтобы я мог обращаться к элементам по индексу? Рассмотрим ICollection<T>
  • Является ли порядок, в котором я добавляю элементы в сборку? Может быть, это ISet<T> ?
  • Если вы действительно хотите эту вещь, тогда идите и реализуйте IList<T> .

Таким образом, вы не будете связывать другие части кода с деталями реализации вашей коллекции бейсболистов и сможете свободно изменять, как это реализовано, если вы уважаете интерфейс.

Используя этот подход, вы обнаружите, что код становится легче читать, рефакторировать и повторно использовать.

Заметки об исключении котельной

Внедрение интерфейсов в современной среде IDE должно быть простым. Щелкните правой кнопкой мыши и выберите "Использовать интерфейс". Затем переместите все реализации в класс-член, если вам нужно.

Тем не менее, если вы обнаружите, что вы пишете много шаблонов, это потенциально потому, что вы подвергаете больше функций, чем должно быть. Это то же самое, что вы не должны наследовать от класса.

Вы также можете разрабатывать меньшие интерфейсы, которые имеют смысл для вашего приложения, и, возможно, просто несколько вспомогательных функций расширения, чтобы сопоставить эти интерфейсы с любыми другими, которые вам нужны. Это подход, который я использовал в моем собственном интерфейсе IArray для библиотеки LinqArray .

0
ответ дан cdiggins 07 окт. '18 в 8:01 2018-10-07 08:01

Я думаю, что не согласен с вашим обобщением. Команда - это не просто коллекция игроков. У команды есть гораздо больше информации об этом - имя, эмблема, сборник управленческого/административного персонала, сбор коучинговой команды, а затем сбор игроков. Таким образом, ваш класс FootballTeam должен иметь 3 коллекции, а не собственную коллекцию; если он должен правильно моделировать реальный мир.

Вы можете рассмотреть класс PlayerCollection, который, как специализированный StringCollection, предлагает некоторые другие возможности - например, проверку и проверку перед добавлением или удалением объектов из внутреннего хранилища.

Возможно, понятие PlayerCollection лучше подходит для вашего предпочтительного подхода?

 public class PlayerCollection : Collection<Player> { } 

И тогда FootballTeam может выглядеть так:

 public class FootballTeam { public string Name { get; set; } public string Location { get; set; } public ManagementCollection Management { get; protected set; } = new ManagementCollection(); public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection(); public PlayerCollection Players { get; protected set; } = new PlayerCollection(); } 
0
ответ дан Eniola 09 июля '16 в 20:14 2016-07-09 20:14

Другие вопросы по меткам или Задайте вопрос