Sunday, March 30, 2014

Serial Reader (პროექტი CL / C#)



მოგესალმებით ჩემი მორიგი პოსტით, რომელიც შეიცავს ინფორმაციას სერიული წამკითხველის შესახებ (რესურს ფაილებსა და საინსტალაციო ვერსიას). ზემოთ მოცემული პროგრამა "Serial Reader" დავწერე C# ში .NET 4 CP პლათფორმაზე დაფუძნებით, რადგან მეოთხე ვერსია უფრო გავრცელებულია ვიდრე 4.5, სიახლის გამო. მოცემული პროგრამა პიდაპირ უმიზნებს და გამოიყენება სპექტრის ანალიზატორისთვის, რომელიც შეგიძლიათ ნახოთ ჩემს ბლოგზე.
პროგრამა დაწერილია WF ის გამოყენებით. WPF ში უფრო დახვეწილი დიზაინი იქნებოდა, მაგრამ ჯერ ჯერობით ჩემს ინტერესებში არ შედის ვიზუალური მიმზიდველობა ამ პროგრამაში. რაც შეეხება ინტერფეისს საკმაოდ მარტივია, ფუნქციიდან გამომდინარე.

პროგრამის ჩართვისას ირჩევთ სერიული პორტის სახელს, რომელზეც თქვენი არდუინოა მიერთებული ან რაიმე მოწყობილობა სერიული ინტერფერისით. ამის შემდგომ სერიული პორტის სიხშირეს და ვაჭერთ ღილაკს SC (Start Communication). ამის შემდეგ Serial Reader ფორმა გაუჩინარდება და გაიხსენება Graph ფორმა, რომელშიც პროგრამის ძირითადი ნაწილი სრუდება. 

კომუნიკაციის პროტოკოლი კი შემდეგნაირია: კომპიუტერი უგზავნის char ტიპის ცვლადს მიკროპროცესორს (ჩემს შემთხვევაში 's') და მიკროპროცესორი ცნობს ამ სიმბოლოს. საპასუხოდ ის კომპიუტერს უგზავნის 128 ბაიტს, რომელსაც კომპიუტერი კითხულობს. გრაფიკის აფსცისათა ღერძი დაყოფილია 128 წერტილად, რადგან ინფორმაციის მოცულობა ამდენივეა, ხოლო ორდინატათა ღერძზე აიღება გამოგზავნილი ინფორმაციის შესაბამისი მნიშვნელობები. 

მაგალითად:
data [0] = 21;
data [1] = 23;
data [2] = 34;
...
data [127] = 54;

შესაბამისად აიღება კოორდინატები, სადაც პირველი კოორდინატი x ია ხოლო, მეორე y და i იცვლება 0-დან 127 მდე (<128). წერტილები კი ასე აიღება point ( i , data[i] ).

როდესაც დაიწყება კომუნიკაცია და გამოჩნდება მეორე, გრაფიკის ფორმა, კომუნიკაციის საბოლოოდ დასაწყებად საჭიროა გრაფიკზე დავაწკაპოთ, რაც საბოლოოდ უკვე მოგცემთ ვიზუალურ შედეგს, ამის შემდეგ, როდესაც უკვე საჭირო მუშაობას მორჩებით კიდევ ერთხელ უნდა დააჭიროთ გრაფიკზე და ის გაშეშდება, რაც იმას ნიშნავს, რომ კომუნიკაცია დასრულებულია. თუ ფორმას გამორთავთ გამოჩნდება საწყისი ფორმა, რომელიც საშუალებას მოგცემთ კომუნიკაციის ხელახლა დაწყების, თუ წინა არ გსურთ, მაშინ უბრალოდ საწყისი ფორმაც გამორთეთ. 

ქვემოთ მოცემულია რესურს ფაილები, სადაც მთლიანი პროექტია მოცემული თავისი კოდი (SourceFile), ხოლო არის ასევე მზა საინსტალაციო პროგრამა, რომელიც მარტივად დასაყენებელია (InstallFile).

Wednesday, March 26, 2014

აუდიო სპექტრზე დაფუძნებულო ეკვალაიზერი




ეს პროექტი დაფუძნებულია ჩემს წინა პროექტზე - სპექტრის ანალიზატორზე. ჩემი აზრით, ეს პროექტი საკმაოდ ლოგიკური გაგრძელებაა ძირითადი პროექტისა, რომელიც საკმაოდ რთული გამოდგა გასალეთებლად.
მოკლედ რომ მოვჭრა, გამაძლიერებლის სქემა იგივეა, რაც პროექტ CL ში. კოდიც იმავეა, რაც ამ პროექტში, მაგრამ დამატებულია რამდენიმე მეთოდი, რომელიც უზრუნველყოფს სიხშირეების დეტექტირებას და 595 შიფტ რეგისტრის გავლით LED ის მართვას, რომელიც თავიდან რვა იყო, ახლა კი ოთხია, რაც, ჩემი აზრით აღარ არის საჭირო, რადგან ამ მიკროსქემის მართვას 3 პინი სჭირდება, ხოლო ერთი პინი, რომ დავამატოთ მივიღებთ 4 LED ის მართვას.

აქ მოცემულია კოდი 595 შიფტ რეგისტრის სამართავად, რომელიც მე მაქვს გამოყენებული, ხოლო კიდევ დამატებულია სურათი, რომელიც საჭიროა 2 ჩიპის სამართავად, ხოლო ჩემს შემთხვევაში გამოყენებულია მხოლოდ ერთი








კონტექსტური მენიუ

მოგესალმებით, ქვემოთ მოცემულია კონტექსტური მენიუ, რომელიც გაგიადვილებთ ნავიგაციას და აღარ მოგიწევთ ბლოგის ძველ პოსტებში ხეტიალი სასურველი პოსტის აღმოსაჩენად, ყველაფერი თავმოყრილი იქნება ერთ პოსტში
W P F
C #



მიკრო პროცესორები


ჩემი პროექტები


Sunday, March 9, 2014

აუდიო სპექტრის ანალიზატორი (CL)


სპექტრის ანალიზატორი ზომავს ამპლიტუდის დამოკიდებულებას სიხშირეზე. ხოლო აუდიო სპექტრის ანალიზატორი არის უფრო კონკრეტული და სპეციალიზებული, რომელიც ზომავს აუდიო სიხშირის დიაპაზონს 20 ჰერციდან 20 კილოჰერცამდე, მიუხედავად იმისა, რომ ადამიანს იშვიათად უწევს მაღალ სიხშირეებთან შეხვედრა. ქვემოთ მოცემულია ჩემი პროექტის აღწერა და იმპლემენტაციის გზა. 


პირველ რიგში ჩამოვთვლი საჭირო მასალას, რასაც საჭიროებს ამ პროექტის განხოციელება :

1. გამაძლიერებლის სქემა, რომელიც ქვემოთ არის მოცემული (შენიშნვა, A წერტილში მდებარე C2 კონდესატორი მოვხსენი, რადგან ასე გახდა საჭირო ჩემს შემთხვევაში, თუ დაბრკოლება არ შეგიქმნათ, მაშინ დატოვეთ!)

გამაძლიერებლისათვის კი შემდეგი ნაწილებია საჭირო
1)  C2 | C3 | C4 = 2.2 მიკროფარადი
2)  C1 = 1 მიკროფარადი, რომელიც 10 მიკროფარადით შევცვალე, პროექტის ინტერესებიდან გამომდინარე
3)  R7 | R8 = 4.7K
4)  R1 | R2 | = 50K - 50K ამ წინაღობების მთავარი აზრი ის არის, რომ მოხდეს ძაბვის გაყოფა შუა წერტილში
5)  R3 | R4 | R5 | R6 - კი გაძლიერების კოეფიციენტებს არეგულირებენ, რომელიც შესაძლებელია ასე გამოითვალოს, პირველი გამაძლიერებლისათვის, R4 / R3 ხოლო მეორესათვის R6 / R5
6)  C5 = 100 მიკროფარადი, რომელიც + სა და 0 ს შორის ირთვება
7) ელექტრონული მიკროფონი

8) და მიკროსქემა LM358 ან უბრალოდ დაბალ ხმაურიანი ერთჯერადი კვების საშუალების მქონე ოპ. გამაძლიერებელი. 

სქემა მოკლედ, რომ აღვწერო, გვაქვს ელექტრონული მიკროფონი, რომელიც მარაგდება R7 + R8 წინაღობების გავლით, ეს ორი წინაღობა ჩემს შემთხვევაში შეიძლება ერთით შეცვალოთ, რადგან C2 ის გამოა ეს ყველაფერი. ამის შემდეგ სიგნალი შედის C1 ის გავლით ინვერტული გამაძლიერებლის ერთ კომპონენტში, რომელიც DC კომპონენტს ფილტრავს და ატარებს AC კომპონენტს. ამის შემდეგ ეს სიგნალი ძლიერდება და გადადის მეორე საფეხურზე. რეკომენდირებულია, რომ თითოეული გამაძლიერებლის კოეფიციენტი არ აღემატებოდეს 30ს. ხოლო აღნიშვნები FS და SS, გამოყვანილია x30, რომელსაც ვიყენებ და x900, რომელზეც საკმაოდ ძლიერი სიგნალი მოდის. 

ძაბვა, არის Arduino სთან თავსებადი, რაც იმას ნიშნავს, რომ 5 ვოლტი სავსებით საკმარისია, ხოლო თუ კიდევ 2 გამაძლიერებელ მოდულს დაამატებთ 3,3 ვოლტიც საკმარისი იქნება, მაგრამ უნდა არეგულიროთ გაძლიერების კოეფიციენტი, მე პირადად არ მიცდია. 

2. Arduino ს ნებისმიერი დაფა (Uno / Leonardo ჩემს შემთხვევაში), რომელსაც 1K ზე მეტი RAM ექნება, რადგან ინფორმაციის სწრაფი დამუშავებისათვის არის საჭირო. 

3. FFT ბიბლიოთეკა, რომელიც განთავსებულია შემდეგ ლინკზე, რომელიც აგზავნის 128 წერტილს (ჩემს შემთხვევაში. უფრო ნაკლებიც არის შესაძლებელი). 

4. კომპიუტერი, რომელსაც მე ამ ეტაპზე ვიყენებ გრაფიკის ასაგებად, მაგრამ ეს შესაძლებელია იყოს პროცესორზე მიერთებული თხევადკრისტალური დისფლეი ან LED მატრიცა

5. პროგრამა, რომელიც ჯერ ბოლომდე არ არის დასრულებული და მხოლოდ საცდელი ვარიანტია. პროგრამის ხორცშესასხმელად დაგჭირდებათ System.IO.SerialPort კლასი, რომელიც Arduino ს დაუკავშირდება და სინქრონიზებულად იმუშავებს მასთან. 




დამატებითი ინფორმაციისთვის / პროექტთან დაკავშირებული იდეებისთვის მომწერეთ


Tuesday, February 18, 2014

Generics in C#



ზოგადი ტიპების მიმოხილვა

.NET 2.0 ვერსიის შემდეგ მომხმარებელს უკვე თავისუფლად შეუძლია გამოიყენოს ის თვისება, რასაც ზოგადი ტიპის ფენომენი ეწოდება პროგრამირების ენაში. ეს არ არის მხოლოდ C# ის ნაწილი. ეს არის მთლიანად IL (Intermediate Language) ის დიდი ოჯახის წევრი, რომელშიც ეს თვისება საკმაოდ კარგად არის ინტეგრირებული. ზოგადი ტიპების გამოყენებით, რომელსაც ამიერიდან generic ებით მოვიხსენიებ, მიუხედავად იმისა, რომ ქართული თარგმანიც არსებობს (ზოგადი „ტიპი“), შესაძლებელია ტიპზე დამოუკიდებელი კლასების, ინტერფეისების, მეთოდებისა და სტრუქტურების შექმნა. აღარ არის საჭირო ყოველი ტიპისთვის დაიწეროს სხვადასხვა ოპერაცია, რომლის ლოგიკა ერთია, მაგრამ ტიპია სხვადასხვა. უბრალოდ არსებობს უფრო მარტივი გზა - generics.


მიუხედავად იმისა, რომ ამ სფეროში ხდება ტიპის საკმაოდ ღრმა განზოგადება, მაინც დაცულია ტიპობრივი უსაფრთხოება და შესაძლებელია შეზღუდვების ხელოვნური დაწესება, რომელსაც მოგვიანებით განვიხილავ.
 
 
 
 
Generic ები არ არის ახალი კონსტრუქცია, რომელიც არ არსებობს სხვა ენებში. მაგალითისათვის რომ ავიღოთ C++ ის Template ებს გააჩნიათ რაღაც საერთო generic ებთან. არსებობს ასევე განსხვავებები, რომლებიც იმაში მდგომარეობს, რომ C++ ის template ები მოითხოვენ კოდის წყაროს, რომ გამოიყენოთ ენის ეს თვისება, ხოლო C# ში ეს არ არის მოთხოვნა, რადგან ეს კონსტრუქცია უფრო დაბალ დონეზეა განსაზღვრული - CLR ში. აქედან გამომდინარე შესაძლებელია .NET პლატფორმიდან რომელიმე ენაში განსაზღვრული ტიპი გამოიყენოთ როგორც generic ის ტიპი.
ქვემოთ მოცემულია რამდენიმე მაგალითი, რომლებიც ძალიან ზერელედ შეეხებიან ამ კონსტრუქციას, რადგან უკეთ გაიცნოთ ის, ხოლო ამის შემდეგ უკვე შემოგთავაზებთ უფრო ღრმა ინფორმაციას, რომელიც დაგანახებთ ამ ყველაფრის მნიშვნელობასა და generic ების გამოყენების შედეგად მიღებულ სიმარტივეს.

შესრულების უნარიანობა

ერთ-ერთი ყველაზე დიდი დადებით მხარეთ შეიძლება ჩაითვალოს შესრულების უნარიანობა, რადგან კოლექციებთან მუშაობისას ჩანს ამ კონსტრუქციის დადებითი მხარე. არსებობს კოლექციები, რომლის ტიპი არის object და რომლის ტიპი შეგვიძლია განსაზღვრისას მივუთითოთ. ცვლადის ტიპის გამოყენებისას, არა generic კოლექციებში, ძალიან დიდი მოცულობის რესურსები მიდის value-type ცვლადის reference-type ცვლადში გადაფუთვაში და მისი წაკითხვისას ამ ოპერაციის პირიქით ჩატარებაში.
მომდევნო მაგალით იყენებს System.Collections.ArrayList კლასს, რომლის ობიექტში შეგვიძლია დავამატოთ ნებისმიერი ტიპის ობიექტი Add() მეთოდის გამოყენებით, რომლისა პარამეტრია თვით დასამატებელი ობიექტი. ამ პროცესის მსვლელობისას ერთი საინტერესო რამ ხდება. როდესაც ობიექტს ენიჭება value ტიპის ცვლადი, კომპილატორს ავტომატურად გადაჰყავს value ტიპის reference ტიპში, რასაც boxing/შეფუთვა ეწოდება. ხოლო მის უკან წასაკითხად საჭირო ხდება unboxing საჭირო, რაც შეფუთვის შებრუნებული პროცესია. ეს შებრუნებული პროცესი მაშინ ხდება, როდესაც მომხმარებელს სურს ArrayList ში მოთავსებული ინფორმაციის წაკითხვა, ანუ ობიექტი (რომელიც reference ტიპისაა) გარდაიქმნება value ტიპად. ახლა კი დროა იხილოთ მაგალითი
static void Main(string[] args)

        {
            var nonGenericList = new System.Collections.ArrayList();
            //შეფუთვა - value ტიპი -> reference ტიპი
            nonGenericList.Add(44);

            //unboxing reference ტიპი -> value ტიპი
            int item0 = (int)nonGenericList[0];
        }
Boxing/unboxing ის პროცესი საკმაოდ მარტივი გამოსაყენებელია, მაგრამ რესურსების მხრივ საკმაოდ დიდი დანაკარგი მოჰყვება ამ მარტივ ტექნიკას, როდესაც კოლექციაში წევრთა რიცხვი იზრდება. ამის მაგივრად შესაძლებელია System.Collections.Generic.List<T> ის გამოყენება, რომელსაც T-ს ნაცვლად მიუთითებთ სასურველ ტიპს, რომელზეც მუშაობს მომხმარებელი და ამ შემთხვევაში boxing/unboxing ს უკვე აღარ აქვს ადგილი.

static void Main(string[] args)
        {
            var GenericList = new System.Collections.Generic.List<int>();

            //არ ხდება არანაირი შეფუთვა

            GenericList.Add(44);

            Console.WriteLine(GenericList[0]);

        }

ტიპობრივი უსაფრთხოება


სხვა თვისება რაც ამ კონსტრუქციას ახასიათებს არის ტიპობრივი უსაფრთხოება, რაც იმაში მდგომარეობს, რომ თუ განვიხილავთ ArrayList ის ქვემოთ მოცემულ შემთხვევას, ეს იმას ნიშნავს, რომ ამ ობიექტში ნებისმიერი ტიპის ცვლადის „ჩაყრა“ არის შესაძლებელი.

nonGenericList.Add(231);

nonGenericList.Add("Giorgi");

nonGenericList.Add(new object());

ახლა როდესაც მოვახდენთ კოლექციის წევრების ნახვის ოპერაციას

foreach(int item in nonGenericList)

            {
                Console.WriteLine(item);
            }

მოხდება ის, რომ პირველი წევრის წაკითხვის შემდეგ კომპილატორი ამოაგდებს შეცდომას, რაც იმით იქნება გამოწვეული, რომ მომხმარებელს არ შეუძლია განსაზღვროს რა სახის ტიპებია მოთავსებული ამ სიაში წინასწარ.

ამიტომ შეცდომა რაც შეიძლება ადრე უნდა იქნას არიდებული რაც იმაში გამოიხატება, რომ გამოიყენოთ generic კლასი - List<T>.

var genericList = new List<int>();

genericList.Add(512);

genericList.Add(2014);

genericList.Add("sa"); //შეცდომა, რაც ავტომატურად დაფიქსირდა

ორობითი კოდის ხელახალი გამოყენების საშუალება


რის საშუალებასაც იძლევა generic ები. ეს იმას ნიშნავს, რომ ზოგადი ტიპის კლასი შეიძლება განისაზღვროს მხოლოდ ერთხელ და მოხდეს მისი გამოყენება ნებისმიერ ტიპთან თუ რა თქმა უნდა არ დაწესდა ტიპის შეზღუდვა (რასაც ამ სტატიაში ავხსნი მოგვიანებით). C++ ის Template ებისგან განსხვავებით აქ არ არის საჭირო კოდის წყაროზე წვდომა და როგორც ზემოთ აღვნიშნე .NET ის ერთ ენაში განსაზღვრული ზოგადი ტიპის კლასის გამოყენება სრულებით შესაძლებელია .NET პლატფორმის ნებისმიერ სხვა ენაზე.

კოდის მოცულობა


ამ ინფორმაციის ძებნისას ბევრი რამის გარკვევა მომიხდა და შემიძლია აღვნიშნო, რომ C# და სხვა .NET ის პლატფორმაზე დაწერილი კოდი IL ში გადასვლისას ზოგადი ტიპის კლასებს ათავსებს ასამბლეაში. როდესაც ხდება ამ კლასის გამოყენება სხვა ტიპებისათვის არ გეგონოთ, რომ იქმნება ზოგადი ტიპის კლასის ასლი ზუსტად ამ ტიპისთვის. არა, უბრალოდ ყველა კლასი იყენებს ადგილობრივ კლასს, ხოლო მოგვიანებით JIT კომპილატორის გამოყენებით საჭიროებისამებრ იქმნება ზოგადი ტიპის კლასის მითითებულ ტიპზე ორიენტირებული ვერსია. შექმნამდე კი უბრალოდ 32 ბიტიან  სისტემაში 4 ბიტიანი მისამართი ინახება ზოგადი ტიპის კლასისა.

სახელების დარქმევა და ერთგვარი გიდი


ჩვეულებრივი კლასებისაგან გასარჩევად ჩემი პირადი რჩევაა, რომ გამოიყენოთ შემდეგი მითითებები, რომლებსაც მე ვიყენებ და თავის დროზე ინტერნეტის საშუალებით ვისწავლე

Ø  Generic ების პრეფიქსი უნდა იყოს ასო T (ინტერნეტიდან)

Ø  რადგან ზოგადი ტიპი უნდა ჩანაცვლდეს სხვა კერძო, განსაზღვრული ტიპით, დროებით მარკერად გამოიყენეთ T (ინტერნეტი, რჩევა). Class List<T> { }

ზოგადი ტიპის კლასები


ზოგადი ტიპის კლასის შესაქმნელად უბრალოდ საჭიროა ზემოთ მოცემული მასალის კარგად გაანალიზება და ცოტაოდენი ექსპერიმენტის ჩატარება რაც უკვე ნელ-ნელა პრაქტიკაში გადავა. ქვემოთ გთავაზობთ ორ კლასს, რომელთაგან ერთი გამოიყენებს generics ხოლო მეორე უბრალოდ იქნება მომხმარებლის მიერ განსაზღვრული მაგალითი, რომელსაც Item ეწოდება, და რომლის კოდიც ქვემოთაა მოცემული. წარმოიდგინეთ, რომ ეს Item (ნივთი) არის რაღაც კლასი, რომელსაც აქვს სახელი და მოკლე აღწერა. ეს კლასიც საკმაოდ ზოგადია, მაგრამ არა პროგრამირების მხრივ.

public class Item

    {

        string _name;

        public string Name

        {

            get

            {

                return _name;

            }

            set

            {

                if (value != null)

                    _name = value;

            }

        }

        string _description;

        public string Descr

        {

            get

            {

                return _description;

            }

            set

            {

                if (value != null)

                    _description = value;

            }

        }

 

        public Item(string itemName, string itemDescription)

        {

            Name = itemName;

            Descr = itemDescription;

        }

 

        public override string ToString()

        {

            return string.Format("{0} - {1}", Name, Descr);

        }

    }

ახლა კი თამაშში შემოდის generic ები. ჩემს მიერ წარმოდგენილი კლასი არის უბრალოდ სია, რომელშიც შესაძლებელია მონაცემების დამატება, რომლის ტიპი მომხმარებლის სურვილზეა დამოკიდებული. ასევე შესაძლებელია მონაცემების წაშლა და მთლიანი სიის მიღება. იმის მაგივრად, რომ List<T> ს მაგალითზე მსგავსი კლასი შემექმნა, უბრალოდ List<T> ზე დაყრდნობით შევქმენი ზოგადი ტიპის კლასი, რაც, ჩემი აზრით უფრო გაუადვილებს დამწყებებს ზოგად ტიპებზე ფოკუსირებას და არ გაფანტავს თქვენს ყურადღებას List<T> ის კონსტრუქციაზე და ერთი შეხედვით რთულ კოდზე.

public class GenericList<Type>

    {

        List<Type> internalList = new List<Type>();

 

        public void AddItem(Type item)

        {

            internalList.Add(item);

        }

 

        public void RemoveItem(Type item)

        {

            if (internalList.Contains(item))

                internalList.Remove(item);

        }

 

        public List<Type> GetList()

        {

            return internalList;

        }

    }

საკმაოდ მარტივია List<T> ის გამოყენება, რომელიც იმავე ტიპს მიიღებს, რომელსაც GenericList<Ttpe> კლასის ჩემეული იმპლემენტაცია. ახლა კი ვნახოთ გამოყენების მაგალითი, რომელიც int და Item ტიპზე მაქვს გაკეთებული და საკმაოდ მარტივადაც გამოიყურება.

static void Main(string[] args)

        {

            GenericList<int> integerList = new GenericList<int>();

            integerList.AddItem(2013);

            integerList.AddItem(2014);

 

            GenericList<Item> list = new GenericList<Item>();

            list.AddItem(new Item("C#", "programing language"));

            list.AddItem(new Item("C#", "პროგრამირების ენა"));

        }

მიუხედავად იმისა, რომ მაგალითი პრიმიტიული მოგეჩვენოთ, საკმაოდ კარგად ჩანს ზოგადი ტიპების ძალა. ამის შემდგომ თქვენ შეგიძლიათ ნებისმიერი ტიპისთვის შექმნათ სია, მიუხედავად იმისა, რომ ეს იმპლემენტაცია უკვე არსებობს, თქვენი იდეების განხორციელების საშუალებას მოგცემთ ზემოთ მოცემული მაგალითი.

Generic ების თვისებები


როდესაც ვქმნით ზოგად კლასებს, საჭირო ხდება რაღაც თვისებების გამოყენება, რომელიც სიტუაციურია და საკმაოდ სასარგებლო სამუშაოსას ასრულებს რიგ შემთხვევებში. მაგალითისთვის ვიტყვი, რომ თუ გვსურს, რომ ცვლადი განვსაზღვროთ და არ მივანიჭოთ მას მნიშვნელობა ასე მოვიქცევით

string variable = null;

რაც აბსოლუტურად  ნებადართულია, ხოლო თუ ამას ზოგადი ტიპის კლასებისთვისაც გამოვიყენებთ ვნახავთ, რომ VS შეცდომას ამოგვიგდებს, რადგან ზემოთ მოცემული მაგალითი ზოგადი ტიპის კლასებისთვის ასე „გადაიწერება“

Type variable = default(Type);

რომელიც მოთავსებულია GenericList<Type> კლასში და წარმოადგენს ველს.

შეზღუდვები


თუ ზოგად კლასს სჭირდება, რომ გამოიძახოს მეთოდი ზოგადი ტიპის გავლით, ამ დროს საჭიროა შეზღუდვის დამატება. მოცემული მაქვს DocManager<T> კლასი, რომელსაც გააჩნია ViewAll() მეთოდი. ასევე არის Document კლასი, რომელიც უკეთებს IDoc ინტერფეისს იმპლემენტაციას თვისებებით Title და Content.

public interface IDoc

    {

        string Title { get; set; }

        string Content { get; set; }

    }

 

    public class Document: IDoc

    {

        public Document()

        {

 

        }

 

        public Document(string title, string content)

        {

            this.Title = title;

            this.Content = content;

        }

 

        public string Title { get; set; }

        public string Content { get; set; }

    }

დოკუმენტების საჩვენებლად DocManager<T> ის გამოყენებით, შესაძლებელია T ტიპის ინტერფეისში გადაყვანა, რომ ვნახოთ დოკუმენტის სათაური.

public void ViewAll()

        {

            foreach (T doc in docQueue)

            {

                Console.WriteLine(((IDoc)doc).Title);

            }

        }

პირველადი კოდი დაახლოებით ასეთი სახის იქნება, რომელსაც T ტიპი გადაჰყავს ინტერფეისში და რადგან კომპილერმა იცის, რომ IDoc ინტერფეისს აქვს Title თვისება, თვლის, რომ ზემოთ დაწერილი კოდი სრულფასოვანია და ეს რეალურადაც ასეა. აქ არსებობს ერთი პრობლემა, რომლიც მაშინ წამოიჭრება, თუ DocManager<T> კლასში, ტიპი T არ უკეთებს IDoc ინტერფეისს იმპლემენტაციას. ამ პრობლემის თავიდან ასაცილებლად საჭიროა, რომ T ტიპს დავუწესოთ შეზღუდვა, რომელიც შეამოწმებს იმას მოცემული T ტიპი უკეთებს თუ არა IDoc ინტერფეისს იმპლემენტაციას. ამისთვის კი დაგვჭირდება where საკვანძო სიტყვა და ქვემოთ მოცემული კლასის იმპლემენტაცია.

public class DocManager<T>

        where T: IDoc

    {

        private readonly Queue<T> docQueue = new Queue<T>();

 

        public void ViewAll()

        {

            foreach (T doc in docQueue)

            {

                Console.WriteLine(doc.Title);

            }

        }

 

        public void AddDoc(T doc)

        {

            lock(this)

            {

                docQueue.Enqueue(doc);

            }

        }

 

        public bool IsDocAvailable

        {

            get { return docQueue.Count > 0; }

        }

    }

შესაბამისად რადგან შეზღუდვა მოხსნილია შეგიძლიათ, რომ ტიპის გადაყვანა არც გააკეთოთ და პირდაპირ გამოიტანოთ დოკუმენტის სახელი. ახლა კი ამ ყველაფრის მოქმედებაში მოსაყვანად უნდა განვიხილოთ main მეთოდის კოდი, სადაც ვიყენებ Document კლასის ტიპს DocManager<T> ისათვის, რომელიც იმპლემენტაციას უკეთებს IDoc ინტერფეისს.

static void Main(string[] args)

        {

            var dm = new DocManager<Document>();

            dm.AddDoc(new Document("Title 1", "Content 1"));

            dm.AddDoc(new Document("Title 2", "Content 2"));

 

            dm.ViewAll();

        }

ახლა DocManager<T> მუშაობს ნებისმიერ კლასთან, რომელიც იმპლემენტაციას უკეთებს IDoc ინტერფეისს. არსებობს კიდევ რამდენიმე საშუალება შეზღუდვების დასაწესებლად.

A.      public class Sample<T>  where T: struct

B.       public class Sample<T>  where T: class

C.       public class Sample<T>  where T: interface

D.      public class Sample<T>  where T: Document

E.       public class Sample<T>  where T: new ()

F.      public class Sample<T>  where T: M

შეზღუდვის 6 ვარიანტი არსებობს და აქვე გავარჩევ თითოეულს

A.     სტრუქტურული შეზღუდვა, სადაც T უნდა იყოს value-type

B.      კლასობრივი შეზღუდვა, სადაც T უნდა იყოს reference-type

C.     Where T: interface ნიშნავს, რომ T ტიპი აუცილებლად უნდა უკეთებდეს interface (ნებისმიერი სხვა მითითებული ინტერფეისი) სს იმპლემენტაციას, როგორც ეს ზემოთ მოცემულ მაგალითში გავაკეთე

D.     Where T: Document, სადაც Document კლასი ჩემს მიერ სადემონსტრაციო კლასია (შეიძლება იყოს ნებისმიერი კლასი) და ეს შეზღუდვა ითვალისწინებს იმას, რომ T ტიპი აუცილებლად უნდა იყოს მემკვიდრე კლასი Document ისა

E.      Where T: new() შეზღუდვა ნიშნავს იმას, რომ T ტიპს აუცილებლად უნდა ჰქონდეს სტანდარტული კონსტრუქტორი

F.      ასეთი რამის გაკეთებაც არის შესაძლებელი. ეს შეზღუდვა ნიშნავს იმას, რომ T უნდა იყოს M ზოგადი ტიპის მემკვიდრე. ამას ეწოდება naked type constraint.

ასევე არსებობს ისეთი შემთხვევები, როდესაც შეზღუდვების სია გვაქვს

public class Sample<T>

        where T: IDoc, new()

    {

 

    }

მემკვიდრეობითობა


ამ ფენომენზე საკმაოდ მოკლედ მომიწევს საუბარი, რადგან ბევრი არც არის სათქმელი და გასაგები მათთვის ვინც კარგად ერკვევა მემკვიდრეობითობის მექანიზმში, ხოლო დამწყებთათვის, ალბათ, მოგვიანებით პოსტში მოუწევთ ამის გარჩევა ან სხვა რესურსის გამოყენება.

ზოგადმა ტიპმა შეიძლება გაუკეთოს ზოგად ინტერფეისს იმპლემენტაცია. იგივეა შესაძლებელი კლასის მემკვიდრეობისას. ზოგადი კლასი შეიძლება იყოს ზოგადი კლასის მემკვიდრე

         public class Base<T>

    {

 

    }

 

    public class Derived<T>: Base<decimal>

    {

 

    }

მოთხოვნა ის არის, რომ ინტერფეისის ზოგადი ტიპი უნდა გამეორდეს, ან საბაზო კლასის ტიპი უნდა იქნას მითითებული. როგორც ზემოთაა გაკეთებული. სწორედ ასე შესაძლებელია, რომ მემკვიდრე კლასი შეიძლება იყოს ზოგადი ან არა. მაგალითად, რომ შევქმნა აბსტრაქტული ზოგადი საბაზო კლასი, ის იმპლემენტირებული უნდა რომელიმე კონკრეტული ტიპით მემკვიდრე კლასის განსაზღვრისას. ეს საშუალებას გაძლევთ შექმნათ კერძო ტიპისთვის სპეციალიზაცია, რაც იმას ნიშნავს, რომ ასეთი რამ იქნება

public abstract class Base<T>

    {

        public abstract T Add(T var1, T var2);

        public abstract T Sub(T var1, T var2);

    }

 

    public class Derived<T>: Base<double>

    {

        public override double Add(double var1, double var2)

        {

            return var1 + var2;

        }

 

        public override double Sub(double var1, double var2)

        {

            return var1 + var2;

        }

    }

აბსტრაქტული Base კლასის გამოყენებით შექმნილია Derived კლასი, რომელიც სპეციალიზდება double ტიპისთვის. თქვენ სურვილისებრ შეგიძლიათ შექმნათ string ტიპისათვის და ასე შემდეგ.

სტატიკური წევრები


ზოგადი ტიპის კლასებისათვის სტატიკური წევრები მოითხოვენ ყურადღებას. სტატიკური წევრები ზიარდებიან კლასის მხოლოდ ერთ ობიექტში. მაგალითით იმედია ამას უკეთ გადმოვცემ, სადაც StaticDemo<T> კლასი შეიცავს სტატიკურ ველს sm

public static class StaticDemo<T>

    {

        public static int sm;

    }

რადგანაც ქვემოთ მოცემულია კლასის ორი სხვადასხვა ტიპის ობიექტი, ეს იმას ნიშნავს, რომ ამ კლასისთვის შეიქმნა ორი სხვა კლასი, სადაც ორივეს ცალკე სტატიკური წევრები აქვს

static void Main(string[] args)

        {

            StaticDemo<int>.sm = 21;

            StaticDemo<decimal>.sm = 512;

 

            Console.WriteLine(StaticDemo<int>.sm); //წერს 21

            Console.WriteLine(StaticDemo<decimal>.sm = 512); //წერს 512

        }

ზოგადი ტიპის ინტერფეისები


ზოგადი ტიპების გამოყენება ისევე როგორც კლასებთან, შესაძლებელია ინტერფეისებთან. ამის გარდა, .NET ფლათფორმაც საკმაოდ მრავლად ზოგადი ტიპის შემცველ ინტერფეისს მოიცავს და გვთავაზობს. მაგალითისათვის შემიძლია დაგისახელოთ IComparable<T>, IExtensibleObject<T>, ICollection<T> და სხვა. ინტერფეისის განსაზღვრა საკმაოდ მარტივია, რომელიც იქნება ზოგადი ტიპის, ეს ნაწილი არ მოითხოვს დიდი განმარტებების გაკეთებას, ისედაც ყველაფერი გასაგებია.

ამ ნაწილში ყურადღება გასამახვილებელია იმაზე თუ რამდენად დაიხვეწა კოდი და შემცირდა ხაზები ახალ ვერსიაში. .NET ის წინა (1.0) ვერსიაში საჭირო იყო ქვემოთ მოცემული კოდის დაწერა, რომ IComparable ინტერფეისის იმპლემენტაცია მომხდარიყო. ზოგადად IComparable ინტერფეისის კოდი ასეთია, რომელიც არ იყენებს generic ებს

namespace System

{

    public interface IComparable

    {

        int CompareTo(object obj);

    }

}

public class Person: IComparable //არა ზოგადი ტიპი

    {

        public string LastName { get; set; }

 

        public int CompareTo(object obj)

        {

            Person other = obj as Person;

            return this.LastName.CompareTo(other.LastName);

        }

    }

 

როდესაც ვიყენებთ ზოგადი ტიპის ინტერფეისს, ამ დროს აღარ არის ტიპის გადაყვანა, როგორც მე გავაკეთე object -> Person

public class Person: IComparable<Person> //ზოგადი ტიპი

    {

        public string LastName { get; set; }

 

        public int CompareTo(Person other)

        {

            return this.LastName.CompareTo(other.LastName);

        }

    }

კო და კონტრა ვარიანტულობა


.NET 4 მდე ყველა ინტერფეისი იყო ინვარიანტული. ხოლო ამ ვერსიაში C# ში დაამატეს ისეთი რამ რაც არის კო და კონტრა ვარიანტულობა, რომელმაც მოიცვა ინტერფეისები და დელეგატები. კო და კონტრა ვარიანტულობა არის კონვერტაცია არგუმენტის ტიპსა და დასაბრუნებელ ტიპებს შორის. მაგალითისათვის რომ ავიღოთ ასეთი რამ: არის თუ არა შესაძლებელი Rectangle ტიპის არგუმენტის გატარება იმ მეთოდში, რომლის პარამეტრი არის Shape ტიპის? ეს არის ხშირად გამოყენებადი მაგალითი და სწორედ ამ მაგალითს გავარჩევ.

როგორც ცნობილია Rectangle კლასი არის Shape კლასის მემკვიდრე. ხოლო განვსაზღვრავ Show(Shape obj) { } მეთოდს, რომელიც გამოიყენება Shape ტიპის ობიექტის მისაღებად. ამის შემდეგ შეგიძლიათ ამ მეთოდში გაატაროთ იმ ტიპის არგუმენტი, რომელიც მემკვიდრეა Shape კლასისა, მაგალითად, Rectangle. ქვემოთ მოცემული სინტაქსი აბსოლუტურად აკმაყოფილებს ნორმებს და არ არის არანაირი შეცდომა.

Rectangle rec = new Rectangle { Width = 21, Height = 2 };

Show(rec);

მეთოდის დასაბრუნებელი ტიპები არიან კონტრა ვარიანტულები, რაც იმას ნიშნავს, რომ როდესაც მეთოდი აბრუნებს Shape ტიპს არ შეიძლება Rectangle ტიპის დაბრუნება, რადგან არ არის აუცილებელი Shape იყოს Rectangle ყოველთვის, ხოლო საპირისპირო რამ შესაძლებელია. თუ მეთოდი აბრუნებს Rectangle ტიპის ობიექტს, როგორც ქვემოთაა მოცემული

public Rectangle ShowRec();

ამ მეთოდის მიერ დაბრუნებული ობიექტი შეიძლება ასე გამოვიყენოთ

Shape s = ShowRec();

.NET 4 მდე ზოგადი ტიპებისათვის მსგავსი რამ დაუშვებელი იყო. ხოლო გაფართოების შემდეგ ზოგადმა ტიპებმა შეძლეს კო და კონტრა ვარიანტულობის გამოყენება, კერძოდ დელეგატებსა და ინტერფეისებში. ახლა კი განვსაზღვრავ იმ კლასებს, რომლებიც საჭირო იქნება შემდეგი მაგალითების დემონსტრირებისათვის

public class Shape

    {

        public double W { get; set; }

        public double H { get; set; }

 

        public override string ToString()

        {

            return string.Format("W = {0} || H = {1}", W, H);

        }

    }

 

    public class Rectangle : Shape

    {

 

    }

კო ვარიანტულობა Generic ინტერფეისებში


ზოგადი ინტერფეისი არის კო ვარიანტული თუ ზოგადი ტიპი მონიშნულია out საკვანძო სიტყვის გამოყენებით. ეს მეორე მხრივ, იმასაც ნიშნავს, რომ T ტიპი დაშვებულია მხოლოდ დასაბრუნებელ ობიექტებზე. ჩემს მიერ განსაზღვრული ინტერფეისი IIndexed არის კო ვარიანტული T ტიპის და აბრუნებს ამ ტიპს მხოლოდ წაკითხვის უნარის მქონე ინდექსერის საშუალებით

 public interface IIndexed<out T>

    {

        T this[int index] { get; }

        int Size { get; }

    }

მოცემული ინტერფეისი იმპლემეტირებულია RecLib კლასში. RecLib განსაზღვრავს Rectangle ს ზოგადი ტიპის T სთვის

public class RecLib : IIndexed<Rectangle>

    {

        private Rectangle[] collection = new Rectangle[3]

        {

            new Rectangle {W = 3, H = 5},

            new Rectangle {W = 5, H = 3},

            new Rectangle {W = 2, H = 2}

        };

 

        public static RecLib GetCollection()

        {

            return new RecLib();

        }

 

        public Rectangle this[int index]

        {

            get

            {

                if (index < 0 || index > collection.Length)

                    throw new ArgumentOutOfRangeException("index");

                return collection[index];

            }

        }

 

        public int Size

        {

            get

            {

                return collection.Length;

            }

        }

    }

RecLib.GetCollection() მეთოდი აბრუნებს Collection, რომელიც იმპლემენტაციას უკეთებს IIndexed<Rectangle> ინტერფეისს, ამიტომ შესაძლებელია რომ დაბრუნებული მნიშვნელობა დაენიშნოს rectangle ცვლადს ტიპით IIndexed<Rectangle>. რადგანაც ინტერფეისი არის კო ვარიანტული ისიც კი არის შესაძლებელი, რომ მიღებული ცვლადი დაენიშნოს ობიექტს ტიპით IIndexed<Shape>. Shape არაფერი არ სჭირდება მეტი, რაც შეიძლება Rectangle მა შესთავაზოს. ამ ყველაფრის უკეთ აღსაქმელად იხილეთ ქვემოთ მოცემული კოდი

public MainWindow()

        {

            InitializeComponent();

            IIndexed<Rectangle> rectangles = RecLib.GetCollection();

            IIndexed<Shape> shapes = rectangles;

 

            for(int i = 0; i < shapes.Size; i++)

            {

                MessageBox.Show(shapes[i].ToString());

            }

        }

კონტრა ვარიანტულობა Generic ინტერფეისებში


ზოგადი ტიპის ინტერფეისი კონტრა ვარიანტულია თუ ზოგადი ტიპი მონიშნულია in საკვანძო სიტყვით. ასეთ შემთხვევაში ინტერფეისს საშუალება ეძლევა ზოგადი ტიპი გამოიყენოს მხოლოდ „შესავალ“ ტიპად თავისი მეთოდებისათვის.

public interface IShow<in T>

    {

        void Show(T obj);

    }

 

    public class ShowShape : IShow<Shape>

    {

        public void Show(Shape obj)

        {

            MessageBox.Show(string.Format("{0} , {1}, {2}", obj.H, obj.W, obj.GetType().Name));

        }

    }

ახალი ობიექტი შექმნისას, ტიპით, ShowShape აბრუნებს IShow<Shape>, რომელიც ენიშნება shapeDisplay ცვლადს.

IShow<Shape> shapeDisplay = new ShowShape();

IShow<Rectangle> rectangleDisplay = new ShowShape();

ზოგადი ტიპის სტრუქტურები


კლასების მსგავსად სტრუქტურებიც შესაძლებელია იყოს ზოგადი ტიპის. ისინი ძალიან გვანან ზოგად კლასებს, მხოლოდ მემკვიდრეობითობის გამოკლებით. მაგალითისათვის მოვიყვან სტრუქტურა Nullable<T>, რომელიც განსაზღვრულია .NET გარემოში. შესადარებლად ვიღოთ მონაცემთა ბაზებში არსებული რიცხვი და C# ში არსებული რიცხვი. მონაცემთა ბაზაში შეიძლება, რომ იყოს NULL, ხოლო C# ში ეს დაუშვებელია, რადგან Int32 არის სტრუქტურა და რადგან სტრუქტურები არიან value-type მათი ნულად წარმოდგენა შეუძლებელია. ეს პრობლემა იჭრება მაშინაც, როდესაც საქმე ეხება XML მონაცემების დაკავშირებას .NET მონაცემების ტიპებთან. ეს თავისტკივილი იწვევს ზედმეტი სამუშაოს შესრულებას, რომელიც სპეციალურად დაკავშირებას ეხება.

მეორე მხრივ სტრუქტურა Nullable<T> ის გამოყენების შემდეგ პრობლემას მარტივი გადაწყვეტა უჩნდება.

ზოგადი ტიპის მეთოდები


ზოგადი ტიპის ექსპანსიამ მეთოდებიც მოიცვა და შესაძლებელია ზოგადი ტიპის გამოყენება მეთოდებში, რომელიც მეთოდის განსაზღვრისას ეთითება.

static void Swap<T>(ref T x, ref T y)

        {

            T tempV;

            tempV = x;

            x = y;

            y = tempV;

        }

განსაზღვრა და მათი გამოყენება (ქვემოთ არის მოცემული) ძალზედ მარტივია. ხოლო ეს პოპულარული მეთოდი ზოგადი ტიპის გამოყენებით უფრო პოპულარული ხდება და საკმაოდ რუტინულ კოდის ეფექტური მომგვარებელია. ასევე შესაძლებელია ისეთი მეთოდის განსაზღვრა, რომელიც რაიმე ტიპზე შეასრულებს მოქმედებებს, ან ამ ტიპის მემკვიდრეზე - ანუ ზოგადი ტიპის მეთოდი ტიპობრივი შეზღუდვებით.

static void Swap<T>(ref T x, ref T y)

            where T : Program

        {

            T tempV;

            tempV = x;

            x = y;

            y = tempV;

        }

Program ის მაგივრად შესაძლებელია ნებისმიერი კლასის მითითება, ეს უბრალოდ მაგალითია. Swap<T> მეთოდის გამოძახება, რომელიც მოცემულია შეზღუდვის გარეშე, შემდეგნაირად არის შესაძლებელი.

           int a = 3;

            int b = 32;

            Swap<int>(ref a, ref b);

 

            string fs = "string N1";

            string ss = "string N2";

            Swap<string>(ref fs, ref ss);

ზოგადი ტიპის მეთოდების გამოყენება ასევე შეიძლება დელეგატებთან

ზოგადი ტიპის მეთოდები დელეგატებთან


static T GetEqual<T>(T x, T y)

        {

            y = x;

            return y;

        }

აზრს მოკლებული მეთოდია, მაგრამ მაგალითისათვის გამოდგება. მეთოდი იღებს ორ ზოგადი ტიპის ცვლადს და მეორეს უტოლებს პირველს, შემდეგ კი აბრუნებს მეორეს. Main ფუნქციაში ამის გამოძახება შემდეგნაირად მოხდება

Func<int, int, int> MyDel = GetEqual<int>;

int result = MyDel(2,3);


ინფორმაციის გამოყენების შემთხვევაში გთხოვთ მომწეროთ