c3 reflection atribute interfeteiasimin/csharp/c3 reflection...sintaxa folosita pentru a atasa un...
TRANSCRIPT
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
1
Curs 3
Introspectie metadata – Reflection. Atribute. Interfete.
Cuprins
� Introspectie metadata - Reflection o Clasa Type
o Exemple de utilizare
� Atribute � preconstruite;
� definite de dezvoltator (custom);
o Definirea atributelor
o Interogarea atributelor
o Atribute la nivel de clasa, metode, proprietati, campuri, etc.
o Clasa AttributeUsage
o Parametri pozitionali si parametri cu nume
o Atribute preconstruite : DllImport, CLSCompliant,
STAThread, Serializable, etc.
� Interfete o Clasificare
� preconstruite
� custom
o Declararea interfetelor
o Evenimente, Proprietati si Metode pentru Interfete
o Interfete multiple
o Interfete si ierarhii de clase
o Interfete si Generice
o Derivarea dintr-o interfata generica
o Implementarea explicita a interfetelor
o Interfete generice ca operatori
o Constrangeri la nivel de interfata
o Interfetele IEnumerable si IEnumerator
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
2
Introspectie metadata - Reflection
Spatiul de nume: System.Reflection
Introspectia metadatei (reflection) este procesul prin care un program poate observa si
modifica structura si comportarea sa.
Spatiul de nume System.Reflection contine tipuri ce regasesc informatia despre
assemblies, module, membri, parametri si alte entitati din codul managed prin examinarea
metadatei. Cateva clase din acest spatiu de nume sunt enumerate mai jos. Lista completa in
MSDN.
Class Description
Assembly Represents an assembly, which is a reusable, versionable, and self-describing building block of a common language runtime application.
AssemblyName Describes an assembly's unique identity in full.
ConstructorInfo Discovers the attributes of a class constructor and provides access to constructor metadata.
CustomAttributeData Provides access to custom attribute data for assemblies, modules, types, members and parameters that are loaded into the reflection-only context.
EventInfo Discovers the attributes of an event and provides access to event metadata.
FieldInfo Discovers the attributes of a field and provides access to field metadata.
LocalVariableInfo Discovers the attributes of a local variable and provides access to local variable metadata.
MemberInfo Obtains information about the attributes of a member and provides access to member metadata.
MethodBase Provides information about methods and constructors.
MethodInfo Discovers the attributes of a method and provides access to method metadata.
Module Performs reflection on a module.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
3
ParameterInfo Discovers the attributes of a parameter and provides access to parameter metadata.
PropertyInfo Discovers the attributes of a property and provides access to property metadata.
Clasa Type
Type constituie baza pentru System.Reflection si asigura modalitatea de a accesa
metadata. Obtinerea informatiilor despre ctor, metode, campuri, proprietati si evenimente se
realizeaza prin folosirea membrilor acestei clase.
Un obiect Type reprezinta un tip unic.
Operatorul typeof returneaza un obiect Type.
O referinta la un obiect Type asociat cu un tip poate fi obtinuta in urmatoarele moduri:
• Metoda Object.GetType returneaza un obiect Type ce reprezinta tipul unei instante.
• Metoda statica GetType returneaza un obiect Type ce reprezinta un tip specificat prin numele sau complet.
• Metodele Module.GetTypes, Module.GetType, si Module.FindTypes returneaza obiecte Type ce reprezinta tipurile definite intr-un modul.
• Obiectul System.Reflection.Assembly contine metode pentru a regasi clasele
definte intr-un assembly.
A se consulta declaratia completa in MSDN.
Cand realizam introspectia metadatei trebuie sa avem in vedere urmatoarea ierarhie:
� assembly curent face referire la alti assemblies;
� un assembly este constituit din module;
� un modul contine tipuri;
� un tip contine metode, membri, proprietati, evenimente, etc.
Ierarhia descrisa mai sus este explicata in urmatorul cod.
Assembly curent se obtine cu metoda :
Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
Colectia de assemblies referiti de assembly curent se obtine cu metoda:
a.GetReferencedAssemblies()
Colectia de assemblies incarcati in AppDomain curent se obtine cu metoda :
AppDomain.CurrentDomain.GetAssemblies()
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
4
Modulele continute de un assembly se obtin cu metoda :
GetModules() apelata pe un obiect de tip Assembly.
Tipurile exportate de un assembly se obtin cu metoda :
GetExportedTypes() apelata pe un obiect de tip Assembly.
Membrii unui tip se obtin cu metoda :
GetMembers() apelata pe un obiect Type. Metoda returneaza o colectie de tip
MemberInfo. Proprietatea MemberType din MemberInfo furnizeaza informatii despre tipul
membrului : metoda, proprietate, etc.
Exemplu din MSDN (de analizat acest cod)
using System; using System.Reflection; class Module1 {
public static void Main() { // This variable holds the amount of indenting that // should be used when displaying each line of information. Int32 indent = 0; // Display information about the EXE assembly. Assembly a = System.Reflection.Assembly.GetExecutingAssembly(); Display(indent, "Assembly identity={0}", a.FullName); Display(indent+1, "Codebase={0}", a.CodeBase); // Display the set of assemblies our assemblies reference. Display(indent, "Referenced assemblies:"); foreach (AssemblyName an in a.GetReferencedAssemblies() ) { Display(indent + 1, "Name={0}, Version={1}, Culture={2}, PublicKey token={3}", an.Name, an.Version, an.CultureInfo.Name, BitConverter.ToString (an.GetPublicKeyToken()))); } Display(indent, ""); // Display information about each assembly // loading into this AppDomain. foreach (Assembly b in AppDomain.CurrentDomain.GetAssemblies()) { Display(indent, "Assembly: {0}", b); // Display information about each module // of this assembly. // Un assembly este constituit din module foreach ( Module m in b.GetModules(true) ) { Display(indent+1, "Module: {0}", m.Name); }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
5
// Display information about each type // exported from this assembly. indent += 1; foreach ( Type t in b.GetExportedTypes() ) { Display(0, ""); Display(indent, "Type: {0}", t); // For each type, show its members & their // custom attributes. indent += 1; foreach (MemberInfo mi in t.GetMembers() ) { Display(indent, "Member: {0}", mi.Name); DisplayAttributes(indent, mi); // If the member is a method, // display information about its parameters. if (mi.MemberType==MemberTypes.Method) { foreach ( ParameterInfo pi in ((MethodInfo) mi).GetParameters() ) { Display(indent+1, "Parameter: Type={0}, Name={1}", pi.ParameterType, pi.Name); } } // If the member is a property, display information // about the property's accessor methods. if (mi.MemberType==MemberTypes.Property) { foreach ( MethodInfo am in ((PropertyInfo) mi).GetAccessors() ) { Display(indent+1, "Accessor method: {0}", am); } } } indent -= 1; } indent -= 1; } } // Displays the custom attributes applied // to the specified member. public static void DisplayAttributes(Int32 indent, MemberInfo mi) { // Get the set of custom attributes; // if none exist, just return. object[] attrs = mi.GetCustomAttributes(false); if (attrs.Length==0) {return;} // Display the custom attributes applied to this member. Display(indent+1, "Attributes:");
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
6
foreach ( object o in attrs ) { Display(indent+2, "{0}", o.ToString()); } } // Display a formatted string indented by the specified amount. public static void Display(Int32 indent, string format, params object[] param) { Console.Write(new string(' ', indent*2)); Console.WriteLine(format, param); } }
Alte exemple.
// creare instanta a clasei DateTime DateTime dateTime = (DateTime)Activator.CreateInstance(typeof(DateTime)); // creare instanta a clasei DateTime, folosind constructor cu // parametri (year, month, day) DateTime dateTime = (DateTime)Activator.CreateInstance(typeof(DateTime), new object[] { 2014, 10, 13 });
Creaza instanta din assembly incarcat in mod dinamic
Din urmatorul cod se creaza un assembly numit Test.dll.
namespace Test { public class Calculator { public Calculator() { ... } private double _number; public double Number { get { ... } set { ... } } public void Clear() { ... } private void DoClear() { ... } public double Add(double number) { ... } public static double Pi { ... } public static double GetPi() { ... } } }
Exemple cu acest assembly pentru introspectie metadata.
// incarcare dinamica assembly din fisierul Test.dll Assembly testAssembly = Assembly.LoadFile(@"c:\Test.dll"); // Obtinerea tipului pentru clasa Calculator din assembly // incarcat in mod dinamic Type calcType = testAssembly.GetType("Test.Calculator"); // creare instanta a clasei Calculator object calcInstance = Activator.CreateInstance(calcType);
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
7
// obtinere informatii despre proprietatea: public double Number PropertyInfo numberPropertyInfo = calcType.GetProperty("Number"); // obtinerea valorii proprietatii: public double Number double value = (double)numberPropertyInfo.GetValue(calcInstance, null); // atribuirea unei noi valori pentru proprietatea: // public double Number (calcInstance, 10.0, null); // obtinere informatii despre proprietatea statica Pi: // public static double Pi PropertyInfo piPropertyInfo = calcType.GetProperty("Pi"); // obtinerea valorii proprietatii : public static double Pi double piValue = (double)piPropertyInfo.GetValue(null, null);
// apelarea metodei de instanta Clear(): public void Clear() calcType.InvokeMember("Clear",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, calcInstance, null);
// apelare metoda privata a instantei: private void DoClear() calcType.InvokeMember("DoClear",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, null, calcInstance, null);
// apelare metoda a instantei: public double Add(double number) double value = (double)calcType.InvokeMember("Add",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,null, calcInstance, new object[] { 20.0 });
// apelare metoda statica publica: public static double GetPi() double piValue = (double)calcType.InvokeMember("GetPi",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, null);
// obtinere valoare camp private: private double _number double value = (double)calcType.InvokeMember("_number",
BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic, null, calcInstance, null);
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
8
Atribute
Ideea de baza: autodescrierea componentelor. Compilatorul si CLR pot citi aceste informatii.
De asemenea dezvoltatorul componentei are toate informatiile despre componenta intr-un
singur loc.
Atributele ne furnizeaza informatiile asociate cu tipul definit in C#. Important e ca aceasta
informatie poate fi definita de dezvoltatorul tipului si nu este statica, legata de limbaj.
Aceasta asociere de informatie urmeaza aceleasi principii folosite in dezvoltarea XML.
Putem crea un atribut bazat pe orice informatie dorim.
Exista un mecanism standard pentru definirea atributelor si pentru interogarile membrului sau
tipului la runtime.
Toate atributele, preconstruite sau definite de utilizator, sunt derivate direct sau indirect din System.Attribute.
Atributele mostenesc o anumita comportare implicita:
• atributul poate fi asociat cu orice element tinta;
• poate fi sau nu poate fi mostenit de un element derivat;
• instante multiple pot fi permise sau nu pe acelasi element tinta.
Aceste comportari sunt specificate in AttributeUsageAttribute.
Un atribut este o adnotare ce poate fi plasata pe un element al codului sursa si este folosit
pentru a memora in momentul compilarii, informatii specifice tipului. Aceasta informatie este
memorata in metadata si poate fi accesata fie in timpul executiei aplicatiei, prin procesul
cunoscut sub numele de reflection (introspectie metadata) sau cand un alt utilitar citeste
metadata. Atributele pot schimba comportarea aplicatiei in momentul executiei.
Putem crea propriile noastre clase de atribute, mostenite din Attribute. Definitia unei asemenea clase include numele atributului, comportarea sa implicita si alte informatii.
Exemplu
Urmatorul exemplu creaza si atribuie atribute definite de utilizator unei anumite clase.
Atributul contine numele programatorului si versiunea clasei.
using System;
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct, AllowMultiple=true)] public class Autor : Attribute {
string autorName; public double Version; public Autor(string name) { autorName = name; Version = 1.0; }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
9
public string GetName() { return autorName; } } [Autor("Eminescu")] class FirstClass {
/*...*/
}
class SecondClass // nu are atributul Autor {
/*...*/
}
[Autor("Calinescu"), Autor("Creanga", Version=1.1)] class ThirdClass {
/*...*/
}
// Cod pentru testare class AutorInfo { public static void Main() { PrintAutorInfo(typeof(FirstClass)); PrintAutorInfo(typeof(SecondClass)); PrintAutorInfo(typeof(ThirdClass)); } public static void PrintAutorInfo(Type type) { Console.WriteLine("Autor information for {0}", type); Attribute[] attributeArray = Attribute.GetCustomAttributes(type); foreach(Attribute attrib in attributeArray) { if (attrib is Autor) { Autor autor = (Autor)attrib; Console.WriteLine(" {0}, version {1:f}", autor.GetName(), autor.Version); } } Console.WriteLine(); } }
Rezultatul este: Autor information for FirstClass
Eminescu, version 1.00
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
10
Autor information for SecondClass
Autor information for ThirdClass
Calinescu, version 1.00
Creanga, version 1.10
Definirea atributelor
Un atribut este in momentul de fata o clasa derivata din System.Attribute. [AttributeUsage(AttributeTargets.Class| AttributeTargets.Struct, AllowMultiple=true)] public class Autor : Attribute { // cod }
Sintaxa folosita pentru a atasa un atribut la un tip sau membru este asemanatoare cu cea
folosita la instantierea unei clase.
Interogarea atributelor
Pentru a interoga un tip sau un membru despre atributele atasate acestuia, trebuie sa utilizam
introspectia metadatei (reflection). Vezi exemplul anterior.
Reflection ne permite de a determina in mod dinamic in momentul executiei, caracteristicile
unui tip.
Putem folosi reflection pentru a citi metadata pentru un intreg assembly si a produce o lista a
tuturor claselor, tipurilor si metodelor definite pentru acel assembly.
Parametrii pozitionali si parametri cu nume
In exemplele anterioare, am examinat atasarea atributelor via constructorul lor.
Vom examina cateva probleme legate de constructorul atributelor.
La inceputul cursului am folosit urmatoarea declaratie :
[Autor("Calinescu"), Autor("Creanga", Version=1.1)] class ThirdClass {
/*...*/
}
Observatie
In momentul cand are loc introspectia metatdatei pentru atribute custom, se vor crea
urmatoarele instante ale clasei Autor:
Autor autor1 = new Autor("Calinescu"); Autor autor2 = new Autor("Creanga"); autor2.Version = 1.1;
Ctor Autor are ca parametru un string. Version (data membru a clasei) este declarat public
in clasa si este setat folosind numele acestuia.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
11
Putem folosi valori implicite folosind parametri pozitionali si parametri cu nume.
Parametrii pozitionali sunt parametrii din constructorul atributului.
Acestia trebuiesc specificati de fiecare data cand folosim atributul.
Parametrii cu nume nu sunt definiti in constructorul atributului, acestia sunt campuri
publice nestatice si proprietati (am folosit in primul exemplu din acest curs).
Parametrii cu nume permit clientului sa seteze atributul campurilor si proprietatilor cand
atributul este instantiat, fara a avea nevoie sa cream un constructor pentru fiecare combinatie
posibila de campuri si proprietati.
Fiecare ctor public poate defini un sir de parametri pozitionali.
Utilizatorul poate referentia ca parametru cu nume orice camp care nu este readonly, static
sau const sau orice proprietate ce include un accesor set – setter – care nu este statica.
Greseli cu parametrii cu nume
Cand folosim parametri cu nume, trebuie sa specificam parametri pozitionali mai intai, si apoi
parametri cu nume, acestia din urma in orice ordine dorim.
Compilatorul incearca sa rezolve mai intai parametrii cu nume si apoi ce ramane, incearca sa
gaseasca un ctor cu signatura potrivita.
Parametrii cu nume pot fi orice camp sau proprietate accesibila publica – incluzind o
metoda setter – care nu este statica sau constanta.
Tipurile parametrilor pozitionali si ai celor cu nume pentru o clasa atribut sunt limitate la
tipurile parametrului atributului, si care poate fi:
• bool, byte, char, double, float, int, long, short, string
• System.Type
• object
• tipul enum cu conditia ca oriunde se gaseste definit sa fie accesibil in mod public.
• un tablou unidimensional ce are ca tipuri de elemente unul din cele de mai sus.
Atributul AttributeUsage
AttributeUsage defineste modul cum sunt folosite atributele.
Sintaxa este:
[AttributeUsage( validon, AllowMultiple = allowmultiple, Inherited = inherited )]
Observam un parametru pozitional, validon, si doi cu nume, AllowMultiple si Inherited.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
12
Parametrul validon
Parametrul validon este de tipul AttributeTargets, si permite sa specificam tipurile pe
care atributul poate fi atasat. Valorile posibile sunt date in enumerarea AttributeTargets.
public enum AttributeTargets { Assembly = 0x0001, Module = 0x0002, Class = 0x0004, Struct = 0x0008, Enum = 0x0010, Constructor = 0x0020, Method = 0x0040, Property = 0x0080, Field = 0x0100, Event = 0x0200, Interface = 0x0400, Parameter = 0x0800, Delegate = 0x1000, All = Assembly │ Module │ Class │ Struct │ Enum │ Constructor │ Method │ Property │ Field │ Event │ Interface │ Parameter │ Delegate, ClassMembers = Class │ Struct │ Enum │ Constructor │ Method │ Property │ Field │ Event │ Delegate │ Interface, }
Implicit este AttributeTargets.All.
Membrii din enumerare pot fi folositi cu operatorul OR (|) ca in exemplul:
[AttributeUsage(AttributeTargets.Field │ AttributeTargets.Property)]
Folosim acest parametru cand vrem sa controlam exact modul de utilizare al unui atribut.
Pentru a fi siguri ca atributele pe care le-am scris sunt folosite pentru tipurile pentru care au
fost proiectate, putem indica in definitia atributului pentru ce este proiectat (elementul tinta pe
care se aplica):
Atribut ce poate fi aplicat tipului class.
[AttributeUsage(AttributeTargets.Class)] public class RemoteObjectAttribute : Attribute {}
Atribut ce poate fi aplicat metodelor. [AttributeUsage(AttributeTargets.Method)] public class AtributTranzactie : Attribute {}
Atribut ce poate fi aplicat campurilor. [AttributeUsage(AttributeTargets.Field)] public class FieldAttribute : Attribute {}
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
13
[RemoteObject(RemoteServers.COSETTE)] class SomeClass { [AtributTranzactie] public void Foo() {} [FieldAttribute("Bar", An = Informatica.AN_4)] public int Bar; }
Parametrul AllowMultiple
Ne permite sa definim un atribut ce poate fi folosit o singura data sau de mai multe ori pe
acelasi element tinta.
Implicit toate atributele sunt folosite ca single, deci nu putem declara de doua ori acelasi
atribut pentru acelasi element tinta. Vezi exemplul:
public class SomethingAttribute : Attribute { public SomethingAttribute(String str){} } // Error: "Duplicate single-use Something attribute" [Something("abc")] [Something("def")] class MyClass{}
Parametru AllowMultiple rezolva aceasta problema.
AllowMultiple = true – atribut multiplicat;
= false – atribut single.
Vezi si exemplul:
[AttributeUsage(AttributeTargets.All, AllowMultiple=true)] public class SomethingAttribute : Attribute { public SomethingAttribute(String str){} } [Something("abc")] [Something("def")] class MyClass{}
Parametrul Inherited
Acest parametru ne spune daca atributul poate fi mostenit sau nu. Valoarea implicita este
false.
Parametrii AllowMultiple si Inherited trebuiesc considerati impreuna. Vezi tabelul de
mai jos.
Inherited AllowMultiple Result
true false Atributul derivat suprascrie atributul
de baza.
true true Atributele derivate si cele din clasa de
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
14
Inherited AllowMultiple Result
baza sunt combinate.
Exemplu:
using System; using System.Reflection; namespace AttribInheritance { [AttributeUsage( AttributeTargets.All, // AllowMultiple=true, AllowMultiple=false, Inherited=true )] public class SomethingAttribute : Attribute { private string name; public string Name { get { return name; } set { name = value; } } public SomethingAttribute(string str) { this.name = str; } } [Something("abc")] class MyClass { } [Something("def")] class Another : MyClass { } class Test { [STAThread] static void Main(string[] args) { Type type = Type.GetType("AttribInheritance.Another"); foreach (Attribute attr in type.GetCustomAttributes(true)) // type.GetCustomAttributes(false)) { SomethingAttribute sa = attr as SomethingAttribute; if (null != sa) { Console.WriteLine("Custom Attribute: {0}", sa.Name); } } } }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
15
}
Cu AllowMultiple = false, rezultatul este: Custom Attribute: def
iar cu AllowMultiple = true, rezultatul devine:
Custom Attribute: def
Custom Attribute: abc
Identificatorii atributului
Deoarece o metoda returneaza un tip si are parametri (cazul general), un atribut aplicat unei
metode poate crea confuzie in sensul ca nu stim daca se aplica pentru valoarea returnata sau
pentru parametrii metodei.
Trebuie sa precizam la ce se aplica un atribut: la o metoda sau la valoarea returnata de
metoda.
Din acest punct de vedere urmatorul cod este ambiguu.
class SomeClass { [HRESULT] public long Metoda() { return 0; } }
In COM, HRESULT reprezinta valoarea returnata de metodele din interfata (mai putin
AddRef si Release, acestea returneaza ULONG).
Putem defini atributul HRESULT astfel:
public class HRESULTAttribute : Attribute { public HRESULTAttribute(){} }
Alte ambiguitati sunt date de urmatoarele situatii:
1. Metode vs. tip returnat;
2. Evenimente vs. camp vs. proprietate ;
3. Delegate vs. tip returnat;
4. Proprietate vs. accessor vs. valoare returnata din getter vs. valoarea parametrului
unei metode set;
Pentru a fixa lucrurile, putem folosi identificatorul atributului, listat mai jos:
• assembly • module • type • method • property • event
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
16
• field • param • return
Sintaxa este:
identificator:atribut
Atentie la : Vezi si exemplul de mai jos.
class SomeClass { [HRESULT] public long Foo() { return 0; } [return: HRESULT] public long Bar() { return 0; } }
Alte exemple:
class SomeClass { [method: HRESULT] public long Foo() { return 0; } [return: HRESULT] public long Bar() { return 0; } [property: HRESULT] public long Goo { get { return 12345; } } }
Vom folosi reflection pentru a determina identificatorul atributului, ca in exemplul urmator:
static void Main(string[] args) { Type type = Type.GetType("AttribIdentifiers.SomeClass"); foreach (MethodInfo m in type.GetMethods()) { foreach (Attribute a in m.GetCustomAttributes(true)) { if (a is HRESULTAttribute) { Console.WriteLine( "method: {0}, " + "CustomAttributes: {1}", m.Name, a); } } ICustomAttributeProvider icap = m.ReturnTypeCustomAttributes; foreach (Attribute a in icap.GetCustomAttributes(true)) { Console.WriteLine( "method: {0}, "
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
17
+ "ReturnTypeCustomAttribs: {1}", m.Name, a); } } foreach (MemberInfo m in type.GetProperties()) { foreach (Attribute a in m.GetCustomAttributes(true)) { Console.WriteLine( "property: {0}, " + "CustomAttributes: {1}", m.Name, a); } } }
Rezultatul este:
method: Foo, CustomAttributes: AttribIdentifiers.HRESULTAttribute
method: Bar, ReturnTypeCustomAttribs:
AttribIdentifiers.HRESULTAttribute
property: Goo, CustomAttributes: AttribIdentifiers.HRESULTAttribute
Atribute predefinite
Cadrul de lucru .NET ofera clase de atribute predefinite, dintre care mai importante sunt:
Predefined .NET
Attribute Valid Targets Description
AttributeUsage Class Specifies the valid usage of another attribute class.
CLSCompliant All Indicates whether a program element is compliant
with the Common Language Specification (CLS).
Conditional Method Indicates that the compiler can ignore any calls to
this method if the associated string is defined.
DllImport Method Specifies the DLL location that contains the
implementation of an external method.
MTAThread Method (Main) Indicates that the default threading model for an
application is multithreaded apartment (MTA).
NonSerialized Field Applies to fields of a class flagged as Serializable;
specifies that these fields won’t be serialized.
Obsolete
All except
Assembly, Module,
Parameter, and
Return
Marks an element obsolete—in other words, it
informs the user that the element will be removed
in future versions of the product.
ParamArray Parameter Allows a single parameter to be implicitly treated
as a params (array) parameter.
Serializable Class, struct, enum,
delegate
Specifies that all public and private fields of this
type can be serialized.
STAThread Method (Main) Indicates that the default threading model for an
application is STA.
StructLayout Class, struct Specifies the nature of the data layout of a class or
struct, such as Auto, Explicit, or Sequential.
ThreadStatic Field (static) Implements thread-local storage (TLS)—in other
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
18
Predefined .NET
Attribute Valid Targets Description
words, the given static field isn’t shared across
multiple threads and each thread has its own copy
of the static field.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
19
Atributele DllImport si StructLayout
Codul C# poate apela functii in cod nativ ce se gaseste in biblioteci dinamice (extensia .dll).
Aceasta caracteristica a runtime-ului se numeste platform invoke.
Exemplu
Vom apela functia Win32 API MessageBoxA ce se gaseste in user32.dll. Codul este:
public class Test { [DllImport ("user32.dll")] public static extern int MessageBoxA ( int h, string m, string c, int type); [STAThread] public static void Main(string[] args) { MessageBoxA(0, "Hello World", "nativeDLL", 0); } }
Runtime .NET transmite parametrii din codul C# manged catre codul nativ din DLL.
Sintaxa completa pentru DllImport este:
[DllImport("user32", EntryPoint="MessageBoxA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern int MessageBoxA ( int h, string m, string c, int type);
Pentru a transmite structuri din codul managed catre codul unmanaged si invers trebuie sa
folosim atributul StructLayout, ce are ca efect construirea structurii exact ca in declaratia ei initiala (adica secvential). Este obligatoriu acest lucru.
Exemplu pentru apelul functiei GetLocalTime(). [StructLayout(LayoutKind.Sequential)] public class SystemTime { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMilliseconds; }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
20
In continuare putem apela functia astfel:
public static void Main(string[] args) { SystemTime st = new SystemTime(); GetLocalTime(st); string s = String.Format("date: {0}-{1}-{2}", st.wMonth, st.wDay, st.wYear); string t = String.Format("time: {0}:{1}:{2}", st.wHour, st.wMinute, st.wSecond); string u = s + ", " + t;
MessageBoxA(0, u, "Now", 0); }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
21
Interfete
Interfetele ne dau posibilitatea de a defini o multime de metode, proprietati, indexeri si
evenimente pe care o clasa le poate implementa. Clasa trebuie sa fie derivata din acea
interfata ceea ce este echivalent cu implementarea in cadrul clasei a metodelor definite in
interfata.
Din punct de vedere conceptual interfetele sunt contracte intre doua parti de cod separate.
Scenariul este urmatorul:
• Se defineste o interfata; interfata defineste o anumita comportare.
• Se defineste o clasa ce implementeaza aceasta interfata, clasa este derivata din
interfata.
• Clientii vor utiliza clasa ce a implementat interfata.
O clasificare a interfetelor ar putea fi :
� interfete din BCL / FCL (IComparer, IDisposable, ICloneable, etc.). � interfete definite de utilizator.
Ne vom indrepta atentia asupra interfetelor definite de catre utilizator.
Utilizarea interfetei
In C#, o interfata este un concept de “prima clasa”, adica o trasatura preconstruita a
limbajului, ce declara un tip referinta si include numai declaratiile metodelor. Proprietatile
sunt metode.
Declararea interfetelor
Interfetele pot contine metode, proprietati, indexeri si evenimente, si nici unul nu este
implementat de interfata insasi.
Exemplu
interface IValidate { bool Validate(); }
Observatie Nu trebuie sa declaram ca metodele sunt pur virtuale (=0).
Interfetele sunt tipuri abstracte si nu pot fi instantiate in mod direct. Pentru a folosi o interfata
trebuie sa convertim (cast) o referinta la interfata la obiectul ce suporta acea interfata.
Exista cast implicit si cast explicit.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
22
Exemplu
interface IFigura { void Draw(); } // Implementare interfata IFigura class Cerc: IFigura { public void Draw() { // cod } }
Cast implicit:
IFigura obj; // IFigura este o interfata obj = new Cerc(); obj.Draw();
Observatie: Eroare la compilare
Daca Cerc nu implementeaza interfata IFigura atunci compilatorul genereaza o eroare.
Controlul sigurantei folosirii tipului are loc la compilare => « early binding ».
Cast explicit :
IFigura obj; // IFigura este o interfata // cod... obj = (IFigura)new Cerc(); obj.Draw();
Observatie: Eroare la executie
La compilare nu mai are loc controlul tipului. Daca tipul Cerc nu implementeaza interfata
IFigura va fi generata o exceptie la executie.
Nu putem folosi cast implicit cand lucram cu clase « factory » non-generice. O clasa “factory”
este o clasa ce construieste un alt obiect. De exemplu tipul Car poate fi construit de un tip numit CarFactory. Avantajul folosirii unui tip ce creaza alt tip este ca numai tipul
« factory » este cuplat la tipurile componentei actuale ce furnizeaza interfetele. Clientul stie
numai aceste interfete. Cand e nevoie de a trece de la un furnizor de servicii la altul trebuie sa
modificam numai « clasele factories » ; clientii nu sunt afectati. Cand folosim class factory ce
returneaza un anumit tip de baza comun, vom folosi cast explicit.
public interface IClassFactory { object GetObject( ); } //… IClassFactory factory; /* cod ce initializeaza class factory */ IFigura obj; obj = (IFigura)factory.GetObject( ); obj.Draw( );
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
23
Observatie: Cand folosim generice nu mai avem nevoie de cast:
public interface IClassFactory<T> { T GetObject( ); } IClassFactory<IFigura> factory; /* cod ce initializeaza class factory */ IFigura obj; obj = factory.GetObject( ); obj.Draw( );
Un alt exemplu:
public interface IFigura { void Draw(); void Area(); } public interface IStoreData { void Save(); } public class Cerc : IFigura, IStoreData { public void Draw() {...} public void Area() {...} public void Save() {...} }
//cod pe partea clientului IFigura obj1; IStoreData obj2; obj1 = new Cerc(); obj1.Draw(); obj2 = (IStoreData)obj1; obj2.Save();
Observatie:
Trebuie sa programam defensiv (utilizare as, is, try...catch).
... obj2 = obj1 as IStoreData; // asemanator cu QueryInterface din COM if (obj2 != null)
obj2.Save(); else
{ // tratare eroare }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
24
Operatorul is
Operatorul is – test existenta interfata
Pentru a testa un obiect daca implementeaza o anumita interfata folosim operatorul is.
Operatorul is ne permite sa controlam in momentul executiei daca un tip este compatibil cu
alt tip. Sintaxa este:
expresie is ttip
Practic, is spune ca o anumita clasa suporta o anumita interfata, dar nu ca o si
implementeaza.
Operatorul as
Operatorul as face conversia intre tipurile compatibile si are urmatoarea sintaxa:
obiect = expresie as tip
unde expresie este un tip referinta.
In urma acestei conversii trebuie scris cod de verificare a conversiei, adica daca obiect are o
valoare valida. Daca conversia nu reuseste obiect are valoarea null.
Implementarea interfetelor
Fiecare clasa ce implementeaza o interfata trebuie sa defineasca fiecare membru al interfetei.
Evenimente, Proprietati, Metode, Indexeri pentru Interfete
Vom explica acestea pe baza de exemple.
Interfata IPrint din exemplul de mai jos defineste metode, proprietati, indexeri si
evenimente.
Observatie Pentru a defini evenimentul trebuie sa cream un delegate.
public delegate void NumberChangedEventHandler(int number); public interface IPrint { void Metoda(); // metoda int Proprietate { get; set; } // proprietate int this[int index]{ get; set;} // indexer event NumberChangedEventHandler NumberChanged; // eveniment } public class MyClass : IPrint { public event NumberChangedEventHandler NumberChanged; public void Metoda() {...}
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
25
public int Proprietate { get {...} set {...} } public int this[int index] { get {...} set {...} } }
Interfete multiple
� In .NET exista mostenire simpla la nivel de clase si mostenire multipla la nivel de
interfete.
� Cand o clasa este derivata dintr-o alta clasa si mai multe interfete, clasa de baza
trebuie sa fie prima in lantul de derivare.
public interface IPrint {} public interface ISave {} public class Baza {} public class Derivata : Baza, IPrint, ISave {}
Coliziuni de nume.
Scenariu 1
O clasa implementeaza doua interfete, interfete ce contin o metoda cu acelasi nume
si parametri.
Regula generala de evitare a coliziunii de nume este prefixarea numelui metodei cu numele
interfetei, lucru cunoscut sub numele de implementarea explicita a interfetelor.
public interface IPrint { void Metoda(); } public interface ISave { void Metoda(); } public class Cerc : IPrint, ISave { void IPrint.Metoda( ) {...} void ISave.Metoda( ) {...} // alte metode si membri }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
26
Utilizare :
Cerc c = new Cerc() ; ((IPrint)c).Metoda() ; ((ISave)c).Metoda();
Scenariu 2.
Clasa de baza contine o metoda ce are nume identic cu al unei metode din interfata.
Cum implementam metoda in clasa derivata din clasa de baza si din interfata?
Rezolvare: implementare explicita a interfetei si cast la utilizarea metodei.
public class Baza { public void Serialize() { Console.WriteLine("Control.Serialize called"); } } public interface ISave { void Serialize(); } public class Derivata : Baza, ISave { public void ISave.Serialize() {...} }
si cast la utilizare.
Scenariu 3. Clasa derivata din clasa ce implementeaza o interfata, are o metoda cu acelasi nume ca cea
din clasa de baza, nume ce corespunde unei metode din interfata
In exemplul urmator metoda Test() este definita in interfata ITest, in clasa de baza Baza si in clasa derivata Derivata.
Rezolvare: In clasa derivata se va implementa metoda Test() ca fiind o metoda “noua” a clasei
derivate, ascunzand metoda din clasa de baza. interface ITest { void Test(); } // Baza implementeaza ITest. class Baza : ITest { public void Test() { Console.WriteLine("Base.Test (implementare ITest)"); } }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
27
class Derivata : Baza { public new void Test() // ascundere metoda din clasa de baza { Console.WriteLine("Derivata.Test"); }}
Apel: Derivata d = new Derivata(); // apel metoda Test din clasa derivata d.Test(); // apel metoda Test definita in interfata ITest si // implementata in clasa Baza ((ITest)d).Test(); Rezultat executie: Derivata.Test Base.Test (implementare ITest) Observatie:
new si casting-ul din ultima linie de cod rezolva problema in mod corect.
Scenariu 4.
Clasa de baza implementeaza o interfata. Metodele din interfata sunt implementate ca virtual
in clasa de baza pentru a permite override in clasa derivata. Cum accesam o asemenea
metoda din clasa de baza avand la dispozitie o instanta a tipului derivat ?
Construim urmatorul exemplu.
interface ITest
{
// Aceasta metoda va fi virtuala in clasa Baza si override in clasa Derivata.
void VirtualTest();
}
// Baza implementeaza interfata ITest.
class Baza : ITest
{
public virtual void VirtualTest()
{
Console.WriteLine("Baza.VirtualTest - implementare VirtualTest..."); }
// Metoda specifica clasei Baza public void MetodaBaza()
{
Console.WriteLine("Baza.MetodaBaza - implementare metoda ...");
} }
class Derivata : Baza {
public override void VirtualTest()
{
// Daca decomentam linia urmatoare, se va apela metoda VirtualTest
// din clasa de Baza
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
28
//base.VirtualTest();
Console.WriteLine("Derivata.VirtualTest - implementare VirtualTest...");
}
}
In clasa de test putem incerca urmatorul cod :
Derivata d = new Derivata();
// apel metoda MetodaBaza – normal, nici o problema
d.MetodaBaza();
// Apel metoda VirtualTest(), este cea din clasa Derivata. d.VirtualTest();
// Conversie obiect Derivata la tip Baza
Baza b = d as Baza; // Apel pe acest obiect al metodei VirtualTest()
// Se va apela VirtualTest() din clasa Baza?
b.VirtualTest();
Rezultatul este ca ultimul apel va fi tot la VirtualTest() din clasa Derivata.
Folosim reflection pentru a vedea ce tip reprezinta obiectul b definit mai sus.
Type type = b.GetType();
Console.WriteLine("type Name = {0}", type.Name);
Rezultatul este : type name = Derivata
Rezolvari posibile.
I. O solutie ar fi sa obtinem un pointer la metoda VirtualTest() din clasa Baza si apoi sa
apelam metoda Invoke() pe acest obiect. Codul este urmatorul :
Derivata child = new Derivata();
// Reflection
MethodInfo mib = typeof(Baza).GetMethod("VirtualTest");
RuntimeMethodHandle handle = mib.MethodHandle;
IntPtr functionPointer = handle.GetFunctionPointer();
Action pf = (Action)Activator.CreateInstance(typeof(Action), child, functionPointer);
pf.Invoke();
Observatie
RuntimeMethodHandle este o structura si reprezinta un handle la
reprezentarea interna a metadatei pentru o metoda.
Metoda GetfunctionPointer() este definita astfel: //
// Summary:
// Obtains a pointer to the method represented by this instance.
//
// Returns: // A pointer to the method represented by this instance.
//
// Exceptions:
// System.Security.SecurityException:
// The caller does not have the necessary permission
// to perform this operation.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
29
[SecurityCritical]
public IntPtr GetFunctionPointer();
Valoarea returnata de GetFunctionPointer() este folosita in metoda
Activator.CreateInstance() pentru a crea o instanta a metodei data de
varaibila “functionPointer”, ce se potriveste cu delegate Action().
II. O alta solutie poate fi data si folosind Delegate. Nu apare instanta tipului Derivata si ca atare nu se incadreaza in cerintele problemei. // Reflection
MethodInfo mib = typeof(Baza).GetMethod("VirtualTest");
// Creare delegate
Delegate del = Delegate.CreateDelegate(typeof(Action), null, mib);
del.DynamicInvoke();
Observatie
Action este un delegate din FCL. Metodele ce se potrivesc cu acest delegate returneaza void
si nu au parametri. In FCL exista mai multe prototipuri pentru Action.
III. O alta solutie ar fi ca metoda VirtualTest() sa contina un parametru bool si implementarea
din clasa Derivata sa foloseasca acest parametru.
interface ITest {
// Aceasta metoda va fi virtuala in clasa Baza si override in clasa Derivata.
void VirtualTest(bool apelBaza);
}
// Nu am mai dat implementarea din clasa Baza
class Derivata : Baza
{ public override void VirtualTest(bool apelBaza)
{
if (apelBaza) base.VirtualTest();
else
{ // cod ... }
}
}
Codul pentru testare este urmatorul:
Derivata d = new Derivata() ; d.VirtualTest(true) ; // apel metoda din clasa Baza d.VirtualTest(false) ; // apel metoda din clasa Derivata
Interfete si ierarhii de clase
Intr-o ierarhie tipica de clase, clasa de baza ar trebui sa fie derivata din interfata, furnizand
polimorfismul cu interfata la toate subclasele. Aceasta clasa de baza trebuie sa defineasca toti
membri interfetei ca fiind virtual astfel incat subclasele ii pot suprascrie - override.
Fiecare nivel al clasei din ierarhie poate suprascrie nivelul precedent.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
30
using System.Diagnostics; // Pentru clasa Trace public interface ITrace { void TraceSelf( ); } public class A : ITrace { public virtual void TraceSelf() { Trace.WriteLine("A"); } } public class B : A { public override void TraceSelf() { Trace.WriteLine("B"); // base.TraceSelf(); // putem apela din clasa de baza } } public class C : B { public override void TraceSelf(){Trace.WriteLine("C");} }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
31
Interfete si Generice
In multe situatii este de folos sa definim interfete pentru clase colectie generice sau
pentru clase generice ce reprezinta articole intr-o colectie. Preferinta pentru clasele generice
este de a folosi interfete generice, de ex. IComparable<T> in loc de IComparable pentru a evita operatiile de boxing si unboxing pe tipurile valoare (operatii consumatoare de timp).
FCL defineste mai multe interfete generice utilizate cu clasele colectii din spatiul de nume
System.Collections.Generic.
Exemplu
public interface IList<T> { void AddHead(T item); void RemoveHead(T item); void RemoveAll( ); }
Putem implementa interfata in mod implicit si sa furnizam un intreg pentru tipul generic.
public class NumberList : IList<int> { public void AddHead(int item){...} public void RemoveHead(int item){...} public void RemoveAll( ){...} // cod ... }
Alt exemplu:
IList<int> list = new NumberList(); list.AddHead(3);
Putem mentine polimorfismul cu interfete generice daca folosim interfata ca parametru
generic:
public class ListClient<T> { public void ProcessList(IList<T> list){...} } IList<int> numbers = new NumberList(); IList<string> names = new NameList(); // derivata din IList<string> ListClient<int> numbersClient = new ListClient<int>(); ListClient<string> namesClient = new ListClient<string>(); // reutilzare cod si algoritmi din ProcessList( ): numbersClient.ProcessList(numbers); namesClient.ProcessList(names);
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
32
Derivarea dintr-o interfata generica
Cand derivam o interfata dintr-o interfata generica, putem proceda asa:
public interface IBaseInterface<T> { void Metoda(T t); } public interface ISubInterface<T> : IBaseInterface<T> {...}
sau
public interface ISubInterface : IBaseInterface<string> {...}
In mod normal cand derivam din interfete generice ar trebui sa procedam asa (pastrare
caracter generic):
public class List<T> : IList<T> { public void AddHead(T item) {...} // restul implementarii } IList<int> numbers = new List<int>( ); IList<string> names = new List<string>( );
Ceva de genul
// Nu se compileaza
public class List<T,U> : IList<T>,IList<U> {...}
nu este permis.
Implementarea explicita a interfetelor generice
Nu sunt probleme deosebite.
public class NumberList : IList<int> { void IList<int>.AddHead(int item) {...} void IList<int>.RemoveHead(int item) {...} void IList<int>.RemoveAll( ) {...} // cod ... }
Implementarea implicita a interfetelor generice ar putea fi asa:
public class List : IList<int>,IList<string> { public void AddHead(int item) {...} public void AddHead(string item) {...} public void RemoveAll( ) {...} // este pentru ambele interfete
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
33
// se creaza confuzie // cod }
In urmatorul caz trebuie sa folosim implementare explicita pentru ca supraincarcarea
metodelor in C# nu ia in considerare tipul returnat.
public interface IList<T> { void AddHead(T item); void RemoveHead(T item); void RemoveAll( ); T GetHead( ); // metoda ce trebuie supraincarcata }
Deci:
public class List : IList<int>,IList<string> { int IList<int>.GetHead( ) {...} string IList<string>.GetHead( ){...} // cod ... }
Interfete generice ca operatori
In C# 2.0, este imposibil sa folosim operatori precum + sau += pe tipuri generice ca
parametri.
Urmatorul cod nu se compileaza:
public class Calculator<T> { public T Add(T argument1, T argument2) { return argument1 + argument2; //nu se compileaza } // cod ... alte metode }
Solutie:
Putem folosi interfete ce definesc operatii generice la nivel de interfata si furnizeaza un tip
concret si implementare la nivel de subclasa :
public interface ICalculator<T> { T Add(T argument1,T argument2); T Subtract(T argument1,T argument2); T Divide(T argument1,T argument2); T Multiply(T argument1,T argument2); }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
34
Clasa Calculator implementeaza interfata ICalculator pentru tipul concret int. public class Calculator : ICalculator<int> { public int Add(int argument1, int argument2) { return argument1 + argument2; } // Restul metodelor }
Constrangeri la nivel de interfata
O interfata poate defini constrangeri pentru tipurile generice ce le utilizeaza.
// T este un tip ce implementeaza interfata IComparable
public interface IList<T> where T : IComparable<T> {...} // T este un tip ce implementeaza ctor implicit public interface IList<T> where T : new() {...} public class List<T> : IList<T> where T : IComparable<T> { public void Remove(T item) {...} // cod ... }
public class ListClient<L,T> where L : IList<T> { public void ProcessList(L list) {...} } public class NumberList : IList<int> {...} ListClient<NumberList,int> client = new ListClient<NumberList,int>( ); NumberList numbers = new NumberList(); client.ProcessList(numbers);
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
35
Covarianta si Contravarianta in interfete
Interfete generice « variant »
Putem declara parametri de tip generic in interfete ca fiind covariant sau contravariant.
Covarianta permite metodelor din interfata sa poata returna tipuri ce sunt “mai jos” in
ierarhia de derivare decat cele definite de parametrii de tip generic.
Contravarianta permite metodelor din interfata sa aiba tipuri de argumente ce sunt « mai
sus » in ierarhie decat cel specificat prin parametrul generic.
Observatie
Clasa de baza se considera « mai jos » in ierarhia de derivare.
O interfata generica ce are parametri de tip generic covarianti sau contravarianti se numeste
« variant ».
O interfata care nu e nici covarianta nici contravarianta se numeste invarianta.
Interfetele generice variant se declara folosind cuvintele cheie in si out pentru parametrii
generici.
out : parametru de tip generic covariant;
in : parametru de tip generic contravariant;
Regula pentru out: Tipul este folosit numai ca tip returnat de metodele din interfata si nu este folosit ca tip al
argumentelor metodelor.
Exemplu: interface ICovariant<out R> { // Corect R GetSomething(); // Urmatoarea instructiune genereaza o eroare la compilare. // void SetSometing(R sampleArg); }
Observatie :
Daca avem un delegate generic contravariant ca parametru al metodei, se poate folosi tipul
ca parametru generic pentru delegate.
Exemplu
interface ICovariant<out R> { void DoSomething(Action<R> callback); }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
36
Regula pentru out: Tipul nu este folosit drept constrangere generica pentru metodele din interfata:
interface ICovariant<out R> { // Urmatoarea instructiune genereaza o eroare la compilare // deoarece putem folosi in constrangeri generice numai // tipuri contravariante sau invariante. // void DoSomething<T>() where T : R; }
in: declarare parametru de tip generic ca fiind contravariant. Poate fi folosit pentru
constrangeri generice.
Tipul contravariant poate fi folosit numai ca tip al argumetelor metodelor si nu ca valoare de
retur.
interface IContravariant<in A> { void SetSomething(A sampleArg); void DoSomething<T>() where T : A; // Urmatoarea instructiune genereaza o eroare la compilare // A GetSomething(); }
E posibil sa folosim covarianta si contravarianta in aceeasi interfata, dar pentru parametri
diferiti (nu e posibil sa avem in si out pentru acelasi parametru).
interface IVariant<out R, in A> { R GetSomething(); void SetSomething(A sampleArg); R GetSetSometings(A sampleArg); } Exemple:
interface ICovariant<out R> { R GetSomething(); } class SampleImplementation<R> : ICovariant<R> { public R GetSomething() { // Cod ... return default(R); } }
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
37
Clasele ce implementeaza interfete “variante” sunt invariante.
// Interfata este covarianta. ICovariant<Button> ibutton = new SampleImplementation<Button>(); // Object este “mai jos” in ierarhia de derivare; // este clasa de baza (direct sau indirect) pentru Button. ICovariant<Object> iobj = ibutton; // Clasa este invarianta (vedeti observatia de mai sus). SampleImplementation<Button> button = new SampleImplementation<Button>(); // Urmatoarea instructiune genereaza o eroare la compilare // deoarece clasele sunt invariante. // SampleImplementation<Object> obj = button;
“Varianta” in interfete generice (C#)
Suportul pentru varianta permite conversia implicita a claselor ce implementeaza aceste
interfete.
.NET Framework 4 introduce suport pentru varianta pe interfetele urmatoare.
• IEnumerable <T> (T is covariant)
• IEnumerator <T> (T is covariant)
• IQueryable <T> (T is covariant)
• IGrouping <TKey, TElement> ( TKey and TElement are covariant)
• IComparer <T> (T is contravariant)
• IEqualityComparer <T> (T is contravariant)
• IComparable <T> (T is contravariant)
Exemplu complet cu interfete covariante si contravariante
Definire interfete si clase ce implementeaza aceste interfete
Etapa I
namespace Variant
{
/// <summary> /// Interfata covarianta
/// </summary>
/// <typeparam name="R"></typeparam>
public interface ICovariant<out R>
{
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
38
R Get();
}
/// <summary> /// Interfata contravarianta
/// </summary> /// <typeparam name="T"></typeparam>
interface IContravariant<in T>
{
void DoPrint(T t);
}
/// <summary> /// Clasa ce implementeaza interfata covarianta
/// Clasa este invarianta
/// </summary> /// <typeparam name="R"></typeparam>
public class ImplementCovariant<R>: ICovariant<R>
{
private R RItem;
public ImplementCovariant(R item)
{ RItem = item;
} public R Get()
{
Console.WriteLine("Get din ImplementCovariant pentru {0}",
RItem.ToString());
return RItem;
}
}
/// <summary>
/// Clasa ce implementeaza interfata contravarianta
/// Clasa este invarianta
/// </summary> /// <typeparam name="T"></typeparam>
public class ImplementContravariant<T>: IContravariant<T>
{
private T TItem;
public ImplementContravariant(T item)
{
TItem = item;
}
public void DoPrint(T t)
{
Console.WriteLine("DoPrint din ImplementContravariant pentru {0}",
t.ToString());
}
}
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
39
Etapa II
Definire ierarhie de clase
Baza
Derivata
DerivataDerivata
namespace Variant
{
// Clasa de baza
public class Baza {
public static int Contor = 0; public string Name { get; set; }
public int ID { get; set; }
public Baza():this("No name")
{
//Console.WriteLine("Ctor Baza. ID = {0}; Name = {1}", ID, Name);
} public Baza(string name)
{
ID = Contor;
Contor++;
Name = name;
Console.WriteLine("Ctor Baza. ID = {0}; Name = {1}", ID, Name);
}
}
public class Derivata : Baza
{
public Derivata():base("Ctor Derivata!")
{ }
public Derivata(string name):base( name)
{ }
}
public class DerivataDerivata : Derivata
{
public DerivataDerivata():base("Ctor DerivataDerivata!")
{ }
}
}
Etapa III
Codul pentru testare
Baza b = new Baza();
DerivataDerivata dd = new DerivataDerivata();
// Demonstratie covarianta ImplementCovariant<DerivataDerivata> idd =
new ImplementCovariant<DerivataDerivata>(dd);
// d este un tip mai putin derivat (Derivata)
Derivata d = idd.Get();
d.Name = "Name changed";
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
40
Type type = d.GetType();
Console.WriteLine("type " + type.FullName);
Console.WriteLine("d Name ={0}, ID = {1}, {2}",d.Name, d.ID, dd.Name);
// Demonstratie contravarianta
ImplementContravariant<Baza> bin = new ImplementContravariant<Baza>(b);
// dd este un tip mai derivat (DerivataDerivata)
bin.DoPrint(dd);
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
41
Construire tipuri enumerabile. Interfetele IEnumerable si IEnumerator.
Sintaxa C# suporta cuvantul cheie foreach pentru a permite iterarea peste continutul oricarui
tip array.
// Iterare peste un array. int[] ints = {10, 20, 30, 40}; foreach(int i in ints) {
Console.WriteLine(i); }
Cod echivalent:
for (int i=0; i < ints.Count; i++) { Console.WriteLine(ints[i].ToString()); }
Observatie
Orice tip ce suporta metoda GetEnumeartor() poate fi evaluat cu constructia foreach.
Implementarea unui tip enumerabil are la baza doua interfete definite in FCL, IEnumerable si IEnumerator.
Interfata IEnumerable este definita astfel: // This interface informs the caller // that the object's subitems can be enumerated. public interface IEnumerable {
IEnumerator GetEnumerator(); }
Metoda GetEnumerator() returneaza o referinta la interfata IEnumerator. IEnumerator furnizeaza infrastructura ce permite iterarea obiectelor interne continute de IEnumerable. // This interface allows the caller to // obtain a container's subitems. public interface IEnumerator {
bool MoveNext (); // Advance the internal position of the cursor. object Current { get;} // Get the current item (read-only property). void Reset (); // Reset the cursor before the first member.
}
Observatie
Tipul System.Array implementeaza IEnumerable si IEnumerator.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
42
Exemplu
public class B {
// cod } public class A: IEnumerable { private B[] tablou = new B[4];
public A() {
// cod pentru completare tablou }
// metoda din interfata IEnumerable
public IEnumerator GetEnumerator() {
return tablou.GetEnumerator(); }
}
In clasa de test putem scrie urmatorul cod:
A a = new A(); // se pp ca in ctor se completeaza “tablou” foreach(B b in a)
Console.WriteLine(b...);
Daca dorim sa ascundem functionalitatea lui IEnumerable de la nivel de obiect, putem
implementa GetEnumerator() astfel: IEnumerator IEnumerable.GetEnumerator() {
return tablou.GetEnumerator(); }
Construire metode de iterare folosind cuvantul cheie yield
Un iterartor este un membru ce specifica cum articolele interne dintr-un container trebuiesc
returnate cand sunt procesate cu foreach. public class A {
private B[] tablou = new B[10]; ... // Metoda pentru iterare public IEnumerator GetEnumerator() {
foreach (B b in tablou) {
yield return b; }
} }
Mai multe informatii in MSDN.
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
43
Probleme propuse
P1. Implementare functionalitate foreach intr-o clasa
Enunt problema
Aveti nevoie de o clasa ce contine o lista de obiecte; fiecare din aceste obiecte contin la randul
lor o lista de obiecte.
Doriti sa folositi o bucla imbricata foreach (adica foreach in foreach) pentru a itera toate
obiectele din ambele clase in modul urmator:
foreach (SubGrup sg in grup)
{
foreach (Item i in sg)
{
// operatii pe obiectele Item continute in sg
// care la randul lui este continut in colectia group
}
}
Indicatie:
Se considera urmatoarele clase:
Group, SubGroup si Item.
Clasa Group mentine o colectie de obiecte SubGroup, iar clasa SubGroup mentine o colectie
de obiecte Item.
Clasa Item are descrierea urmatoare:
public class Item
{
// ctor
public Item(string name, int location)
{
itemName = name;
itemLocation = location;
}
private string itemName = "";
private int itemLocation = 0;
public string ItemName
{
get {return(itemName);}
set {itemName = value;}
}
public int ItemLocation
{
get {return(itemLocation);}
set {itemLocation = value;}
}
}
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
44
Va trebui sa construiti clasele Group si SubGroup precum si metodele aferente.
Scrieti si codul pentru a testa functionalitatea claselor.
Observatie:
Pentru ca o clasa sa poata fi utilizata intr-o bucla foreach aceasta trebuie sa implementeze un
iterator.
Un iterator poate fi o metoda, un operator supraincarcat, sau accesorul get al unei proprietati
ce returneaza fie un obiect de tipul:
o System.Collections.IEnumerator, sau
o System.Collections. Generic.IEnumerator<T>, sau
o System.Collections.IEnumerable, sau
o System.Collections. Generic.IEnumerable<T>
si care contine cel putin o instructiune yield.
P2. Creare enumeratori personalizati
Enunt problema
Aveti nevoie sa adaugati suport pentru foreach la o clasa care sa va dea posibilitatea de a
parcurge colectia in mai multe moduri:
o de la inceput la sfarist, element cu element ;
o de la inceput la sfarsit dar « sarind » peste anumite elemente (in pseudocod ar fi asa :
FOR I = 1 TO N STEP 3 { // cod}).
o de la sfarsit spre inceput element cu element ;
o de la sfarsit spre inceput dar « sarind » peste anumite elemente.
Observatie :
« Salturile » nu trebuie sa fie « hard coded ».
P3. Construire tip ce suporta sortarea
Enunt : Sa se construiasca un tip ce suporta sortarea.
Rezolvare : Se va construi un tip ce este derivat din IComparer sau IComparable.
public class Figura : IComparable {
public Figura(){}
public Figura(int h, int l)
{
this.Inaltime = h;
this.Latime = l;
}
public int Inaltime { get; set;}
public int Latime {get; set; }
public int CompareTo(object obj)
Atribute si interfete
Facultatea de Informatica Iasi – Universitatea Al I. Cuza – Iasi 27.10.2014
Ioan Asiminoaei
45
{
if (this.GetType( ) != obj.GetType( ))
{
throw (new ArgumentException(
"Obiectele comparate trebuie sa fie de tip Figura."));
}
else
{
Figura d2 = (Figura)obj;
long aria1 = this.Inaltime * this.Latime;
long aria2 = d2.Inaltime * d2.Latime;
if (aria1 == aria2)
{
return (0);
}
else if (aria1 > aria2)
{
return (1);
}
else
{
return (-1);
}
}
}
public override string ToString( )
{
return ("Inaltime:" + Inaltime + " Latime:" + Latime);
}
}
// Test
Figura[] tf = new Figura[4]
{
new Figura(1,3),
new Figura(4,3),
new Figura(2,1),
new Figura(6,1)
};
Array.Sort(tf);
Continuati pentru interfata IComparer.
In codul de test ar trebui sa putem scrie ceva de genul :
IComparer ic = new NumeClasaCeImplementeazaIComparer();
Array.Sort(array, ic);
P4. Implementati pattern-ul Observer (Publish / Subscribe) folosind interfete.
Enunt: Clientii se aboneaza la un serviciu meteo si vor sa fie anuntati cand apare o noua
prognoza (1) si/sau conditii meteo deosebite (2) (cod galben : vant, ploi, temperaturi ridicate,
temperaturi scazute, etc.). Implementati pattern-ul Observer pentru tipuri ce se aboneaza la
serviciul (1) sau serviciul (2) sau la ambele servicii (1) si (2).