Dinamik olarak sınıf nasıl oluşturulur?


223

Şöyle bir sınıf var:

public class Field
{
    public string FieldName;
    public string FieldType;
}

Ve List<Field>değerleri olan bir nesne :

{"EmployeeID","int"},
{"EmployeeName","String"},
{"Designation","String"}

Şuna benzeyen bir sınıf oluşturmak istiyorum:

Class DynamicClass
{
    int EmployeeID,
    String EmployeeName,
    String Designation
}

Bunu yapmanın bir yolu var mı?

Bunun çalışma zamanında oluşturulmasını istiyorum. Dosya sistemimde bulunan fiziksel bir CS dosyası istemiyorum.


4
Bu sınıfı çalışma zamanında mı kullanmak istiyorsunuz yoksa yalnızca dosya mı oluşturmak istiyorsunuz?
Damian Leszczyński - Vash

Bu çalışma zamanında oluşturulmasını istiyorum. Dosya sistemimde bulunan fiziksel bir CS dosyası istemiyorum. Daha önce bahsetmediğim için üzgünüm.
ashwnacharya

16
Eğer bize niyetinde şeylerden bir fikir verebilir yapmak bu sınıfla?
Justin

3
@ Justin, örneğin, çalışma zamanı tarafından çözümlenen arabirimleri uygular.
AgentFire

BiriSystem.ServiceModel.ChannelFactory<MyDynamicInterface>
Ilya Semenov

Yanıtlar:


299

Evet, bunun için System.Reflection.Emitad alanını kullanabilirsiniz . Onunla herhangi bir deneyiminiz yoksa düz ileri değildir, ancak kesinlikle mümkündür.

Düzenleme: Bu kod kusurlu olabilir, ama size genel fikir verecektir ve umarım hedefe doğru iyi bir başlangıç ​​için.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace TypeBuilderNamespace
{
    public static class MyTypeBuilder
    {
        public static void CreateNewObject()
        {
            var myType = CompileResultType();
            var myObject = Activator.CreateInstance(myType);
        }
        public static Type CompileResultType()
        {
            TypeBuilder tb = GetTypeBuilder();
            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
            foreach (var field in yourListOfFields)
                CreateProperty(tb, field.FieldName, field.FieldType);

            Type objectType = tb.CreateType();
            return objectType;
        }

        private static TypeBuilder GetTypeBuilder()
        {
            var typeSignature = "MyDynamicType";
            var an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();

            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new[] { propertyType });

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);

            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
}

2
Korku veren !! Ayrıca bana CompileResultType () Yöntemi ile döndürülen türde bir nesne oluşturmayı söyleyebilir misiniz?
ashwnacharya

4
Bunun için System.Activator kullanabilirsiniz. Cevabı bir örnekle güncelleyeceğim.
danijels

4
Ayrıca, dinamik türünüzdeki alanları incelemek, okumak ve güncellemek için yansıma kullanmanız gerekeceğini unutmayın. Eğer akıllı ve yansıma istemiyorsanız, dinamik sınıfınızın devraldığı ve yayınlanabileceği statik bir temel sınıf veya arabirime sahip olmanız gerekir. Bu durumda, GetTypeBuilder () yöntemini değiştirebilir ve moduleBuilder.DefineType çağrısını, son parametre olarak statik türü içerecek şekilde değiştirebilirsiniz (şimdi null)
danijels

2
birisi nesnenin oluşturulduktan sonra nasıl kullanılacağını açıklayabilir
HELP_ME

3
@bugz sınıfı oluşturmak için yukarıdaki kodu kullanın, ardından temel sınıfta bu yöntemi ekleyebilirsiniz: public void SetValue <T> (dize adı, T değeri) {GetType (). GetProperty (name) .SetValue (this, value ); }
stricq

71

Biraz iş alacak, ama kesinlikle imkansız değil.

Ne yaptım:

  • Bir dizede C # kaynağı oluşturun (bir dosyaya yazmanıza gerek yoktur),
  • Üzerinden çalıştırın Microsoft.CSharp.CSharpCodeProvider(CompileAssemblyFromSource)
  • Oluşturulan Türü Bul
  • Ve bu Type ( Activator.CreateInstance) örneğini oluşturun

Bu yolla MSIL yaymak yerine zaten bildiğiniz C # koduyla başa çıkabilirsiniz.

Ancak bu, sınıfınız bazı arayüzler uygularsa (veya bazı temel sınıflardan türetilirse) en iyi sonucu verir, aksi takdirde çağrı kodu (okuma: derleyici) çalışma zamanında oluşturulacak sınıfı nasıl bilebilir?


7
Bu tartışmayı görmek isteyebilirsiniz: yansıma-emit-vs-kodlu
nawfal

1
@nawfal, ashwnacharya, başlangıçta bir listede yer alan üyeleriyle sınıfında dinamik olarak sınıf oluşturmak istedi. Çalışma zamanında bile bir dosyaya koymanın iyi bir performans çözümü olacağını düşünmüyorum.
sodjsn26fr

39

DynamicObject kullanarak dinamik olarak bir sınıf da oluşturabilirsiniz .

public class DynamicClass : DynamicObject
{
    private Dictionary<string, KeyValuePair<Type, object>> _fields;

    public DynamicClass(List<Field> fields)
    {
        _fields = new Dictionary<string, KeyValuePair<Type, object>>();
        fields.ForEach(x => _fields.Add(x.FieldName,
            new KeyValuePair<Type, object>(x.FieldType, null)));
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (_fields.ContainsKey(binder.Name))
        {
            var type = _fields[binder.Name].Key;
            if (value.GetType() == type)
            {
                _fields[binder.Name] = new KeyValuePair<Type, object>(type, value);
                return true;
            }
            else throw new Exception("Value " + value + " is not of type " + type.Name);
        }
        return false;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = _fields[binder.Name].Value;
        return true;
    }
}

Tüm sınıf alanlarını _fieldstürleri ve değerleri ile birlikte sözlükte saklarım. Her iki yöntem de bazı özelliklerin değerini alabilir veya ayarlayabilir. KullanmalısınızdynamicBu sınıfın bir örneğini oluşturmak anahtar kelimeyi .

Örneğinizle kullanım:

var fields = new List<Field>() { 
    new Field("EmployeeID", typeof(int)),
    new Field("EmployeeName", typeof(string)),
    new Field("Designation", typeof(string)) 
};

dynamic obj = new DynamicClass(fields);

//set
obj.EmployeeID = 123456;
obj.EmployeeName = "John";
obj.Designation = "Tech Lead";

obj.Age = 25;             //Exception: DynamicClass does not contain a definition for 'Age'
obj.EmployeeName = 666;   //Exception: Value 666 is not of type String

//get
Console.WriteLine(obj.EmployeeID);     //123456
Console.WriteLine(obj.EmployeeName);   //John
Console.WriteLine(obj.Designation);    //Tech Lead

Düzenleme: Ve işte benim sınıfım şöyle Field:

public class Field
{
    public Field(string name, Type type)
    {
        this.FieldName = name;
        this.FieldType = type;
    }

    public string FieldName;

    public Type FieldType;
}

1
Alanları kurucu ile başlatmam gerekene kadar bu yaklaşımı sevdim. yani dynamic obj = new DynamicClass(fields){EmployeeId=123456;EmployeeName = "John"; Designation = "Tech Lead";}bunu yapmak gerçekten harika olurdu.
rey_coder

14

Bu tür dinamik sınıfların amaçlanan kullanımını bilmiyorum ve kod üretimi ve çalışma süresi derlemesi yapılabilir, ancak biraz çaba gerektirir. Belki Anonim Türler size yardımcı olabilir:

var v = new { EmployeeID = 108, EmployeeName = "John Doe" };

7
Alan adlarını kodlayamazsınız. Onlara kendisininkini veriyor Field.FieldName. Alan adlarını sabit kodlamak zorunda kalmak amacı ortadan kaldırır. Bunu yapmak zorundaysanız, sınıfı da oluşturabilirsiniz.
toddmo

14

Bu eski görevi yeniden açtığımı biliyorum ama c # 4.0 ile bu görev kesinlikle ağrısız.

dynamic expando = new ExpandoObject();
expando.EmployeeID=42;
expando.Designation="unknown";
expando.EmployeeName="curt"

//or more dynamic
AddProperty(expando, "Language", "English");

daha fazla bilgi için bkz. https://www.oreilly.com/learning/building-c-objects-dynamically


Evet, ama burada tip güvenliğini kaybediyorsunuz. Tip güvenliğini korurken benzer bir şey yapabilir miyiz?
toughQuestions

9

CodeDOM'a bakmak istiyorsunuz . Kod öğelerinin tanımlanmasını ve derlenmesini sağlar. MSDN'den alıntı:

... Bu nesne grafiği, desteklenen bir programlama dili için CodeDOM kod üreteci kullanılarak kaynak kodu olarak oluşturulabilir. CodeDOM ayrıca kaynak kodunu ikili bir derleme olarak derlemek için de kullanılabilir.


Bu çalışma zamanında oluşturulmasını istiyorum. Dosya sistemimde bulunan fiziksel bir CS dosyası istemiyorum. Daha önce bahsetmediğim için üzgünüm.
ashwnacharya

1
@ashwnacharya: Sen edebilirsiniz hem kaynak dosyası oluşturma ve zamanında bunu derlemek için CodeDOM kullanın!
Hemant

1
Ancak, CodeDOM derleyicisinin ham bir dize aldığını ve bu nedenle XSS ve SQL enjeksiyonunda kullanılanlara benzer "kod ekleme saldırılarını" dikkate almak isteyebilirsiniz.
cwap

6

@ Danijels'in cevabına dayanarak, VB.NET'te dinamik olarak bir sınıf oluşturun:

Imports System.Reflection
Imports System.Reflection.Emit

Public Class ObjectBuilder

Public Property myType As Object
Public Property myObject As Object

Public Sub New(fields As List(Of Field))
    myType = CompileResultType(fields)
    myObject = Activator.CreateInstance(myType)
End Sub

Public Shared Function CompileResultType(fields As List(Of Field)) As Type
    Dim tb As TypeBuilder = GetTypeBuilder()
    Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)

    For Each field In fields
        CreateProperty(tb, field.Name, field.Type)
    Next

    Dim objectType As Type = tb.CreateType()
    Return objectType
End Function

Private Shared Function GetTypeBuilder() As TypeBuilder
    Dim typeSignature = "MyDynamicType"
    Dim an = New AssemblyName(typeSignature)
    Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)
    Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule")
    Dim tb As TypeBuilder = moduleBuilder.DefineType(typeSignature, TypeAttributes.[Public] Or TypeAttributes.[Class] Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, Nothing)
    Return tb
End Function

Private Shared Sub CreateProperty(tb As TypeBuilder, propertyName As String, propertyType As Type)
    Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.[Private])

    Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing)
    Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes)
    Dim getIl As ILGenerator = getPropMthdBldr.GetILGenerator()

    getIl.Emit(OpCodes.Ldarg_0)
    getIl.Emit(OpCodes.Ldfld, fieldBuilder)
    getIl.Emit(OpCodes.Ret)

    Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, Nothing, {propertyType})

    Dim setIl As ILGenerator = setPropMthdBldr.GetILGenerator()
    Dim modifyProperty As Label = setIl.DefineLabel()
    Dim exitSet As Label = setIl.DefineLabel()

    setIl.MarkLabel(modifyProperty)
    setIl.Emit(OpCodes.Ldarg_0)
    setIl.Emit(OpCodes.Ldarg_1)
    setIl.Emit(OpCodes.Stfld, fieldBuilder)

    setIl.Emit(OpCodes.Nop)
    setIl.MarkLabel(exitSet)
    setIl.Emit(OpCodes.Ret)

    propertyBuilder.SetGetMethod(getPropMthdBldr)
    propertyBuilder.SetSetMethod(setPropMthdBldr)
End Sub

End Class

6

DynamicExpressions kullanarak dinamik olarak bir sınıf da oluşturabilirsiniz .

'Sözlükler kompakt başlatıcılara sahip olduğundan ve anahtar çarpışmalarını ele aldığından, böyle bir şey yapmak isteyeceksiniz.

  var list = new Dictionary<string, string> {
    {
      "EmployeeID",
      "int"
    }, {
      "EmployeeName",
      "String"
    }, {
      "Birthday",
      "DateTime"
    }
  };

Veya serileştirilmiş dize nesnenizi yönetilebilir bir şeyde oluşturmak için bir JSON dönüştürücü kullanmak isteyebilirsiniz.

Sonra System.Linq.Dynamic kullanarak;

  IEnumerable<DynamicProperty> props = list.Select(property => new DynamicProperty(property.Key, Type.GetType(property.Value))).ToList();

  Type t = DynamicExpression.CreateClass(props);

Gerisi sadece System.Reflection kullanıyor.

  object obj = Activator.CreateInstance(t);
  t.GetProperty("EmployeeID").SetValue(obj, 34, null);
  t.GetProperty("EmployeeName").SetValue(obj, "Albert", null);
  t.GetProperty("Birthday").SetValue(obj, new DateTime(1976, 3, 14), null);
}  

4

Dinamik bir sınıf oluşturmak isteyenler için sadece özellikler (yani POCO) ve bu sınıfın bir listesini oluşturun. Daha sonra sağlanan kodu kullanarak, dinamik bir sınıf oluşturur ve bunun bir listesini oluşturur.

var properties = new List<DynamicTypeProperty>()
{
    new DynamicTypeProperty("doubleProperty", typeof(double)),
    new DynamicTypeProperty("stringProperty", typeof(string))
};

// create the new type
var dynamicType = DynamicType.CreateDynamicType(properties);
// create a list of the new type
var dynamicList = DynamicType.CreateDynamicList(dynamicType);

// get an action that will add to the list
var addAction = DynamicType.GetAddAction(dynamicList);

// call the action, with an object[] containing parameters in exact order added
addAction.Invoke(new object[] {1.1, "item1"});
addAction.Invoke(new object[] {2.1, "item2"});
addAction.Invoke(new object[] {3.1, "item3"});

Önceki kodun kullandığı sınıflar şunlardır.

Not: Microsoft.CodeAnalysis.CSharp kitaplığına da başvurmanız gerekir.

       /// <summary>
    /// A property name, and type used to generate a property in the dynamic class.
    /// </summary>
    public class DynamicTypeProperty
    {
        public DynamicTypeProperty(string name, Type type)
        {
            Name = name;
            Type = type;
        }
        public string Name { get; set; }
        public Type Type { get; set; }
    }

   public static class DynamicType
    {
        /// <summary>
        /// Creates a list of the specified type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<object> CreateDynamicList(Type type)
        {
            var listType = typeof(List<>);
            var dynamicListType = listType.MakeGenericType(type);
            return (IEnumerable<object>) Activator.CreateInstance(dynamicListType);
        }

        /// <summary>
        /// creates an action which can be used to add items to the list
        /// </summary>
        /// <param name="listType"></param>
        /// <returns></returns>
        public static Action<object[]> GetAddAction(IEnumerable<object> list)
        {
            var listType = list.GetType();
            var addMethod = listType.GetMethod("Add");
            var itemType = listType.GenericTypeArguments[0];
            var itemProperties = itemType.GetProperties();

            var action = new Action<object[]>((values) =>
            {
                var item = Activator.CreateInstance(itemType);

                for(var i = 0; i < values.Length; i++)
                {
                    itemProperties[i].SetValue(item, values[i]);
                }

                addMethod.Invoke(list, new []{item});
            });

            return action;
        }

        /// <summary>
        /// Creates a type based on the property/type values specified in the properties
        /// </summary>
        /// <param name="properties"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static Type CreateDynamicType(IEnumerable<DynamicTypeProperty> properties)
        {
            StringBuilder classCode = new StringBuilder();

            // Generate the class code
            classCode.AppendLine("using System;");
            classCode.AppendLine("namespace Dexih {");
            classCode.AppendLine("public class DynamicClass {");

            foreach (var property in properties)
            {
                classCode.AppendLine($"public {property.Type.Name} {property.Name} {{get; set; }}");
            }
            classCode.AppendLine("}");
            classCode.AppendLine("}");

            var syntaxTree = CSharpSyntaxTree.ParseText(classCode.ToString());

            var references = new MetadataReference[]
            {
                MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
                MetadataReference.CreateFromFile(typeof(DictionaryBase).GetTypeInfo().Assembly.Location)
            };

            var compilation = CSharpCompilation.Create("DynamicClass" + Guid.NewGuid() + ".dll",
                syntaxTrees: new[] {syntaxTree},
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                var result = compilation.Emit(ms);

                if (!result.Success)
                {
                    var failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    var message = new StringBuilder();

                    foreach (var diagnostic in failures)
                    {
                        message.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }

                    throw new Exception($"Invalid property definition: {message}.");
                }
                else
                {

                    ms.Seek(0, SeekOrigin.Begin);
                    var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);
                    var dynamicType = assembly.GetType("Dexih.DynamicClass");
                    return dynamicType;
                }
            }
        }
    }

Büyük kod parçası. Bir seferde birden fazla kayıt eklemek için dinamik listede AddRange kullanmak da mümkün müdür?
RickyTad

2

İşi yapabilen dinamik modüller ve sınıflar kullanmaya bakabilirsiniz. Tek dezavantajı, uygulama etki alanında yüklü kalmasıdır. Ancak kullanılan .NET framework sürümü ile bu değişebilir. .NET 4.0, toplanabilir dinamik derlemeleri destekler ve böylece sınıfları / türleri dinamik olarak yeniden oluşturabilirsiniz.


2

Vaov! Bu cevap için teşekkürler! Sizinle paylaştığım bir "datatable to json" dönüştürücüsü oluşturmak için bazı özellikler ekledim.

    Public Shared Sub dt2json(ByVal _dt As DataTable, ByVal _sb As StringBuilder)
    Dim t As System.Type

    Dim oList(_dt.Rows.Count - 1) As Object
    Dim jss As New JavaScriptSerializer()
    Dim i As Integer = 0

    t = CompileResultType(_dt)

    For Each dr As DataRow In _dt.Rows
        Dim o As Object = Activator.CreateInstance(t)

        For Each col As DataColumn In _dt.Columns
            setvalue(o, col.ColumnName, dr.Item(col.ColumnName))
        Next

        oList(i) = o
        i += 1
    Next

    jss = New JavaScriptSerializer()
    jss.Serialize(oList, _sb)


End Sub

Ve "compileresulttype" alt bölümünde bunu değiştirdim:

    For Each column As DataColumn In _dt.Columns
        CreateProperty(tb, column.ColumnName, column.DataType)
    Next


Private Shared Sub setvalue(ByVal _obj As Object, ByVal _propName As String, ByVal _propValue As Object)
    Dim pi As PropertyInfo
    pi = _obj.GetType.GetProperty(_propName)
    If pi IsNot Nothing AndAlso pi.CanWrite Then
        If _propValue IsNot DBNull.Value Then
            pi.SetValue(_obj, _propValue, Nothing)

        Else
            Select Case pi.PropertyType.ToString
                Case "System.String"
                    pi.SetValue(_obj, String.Empty, Nothing)
                Case Else
                    'let the serialiser use javascript "null" value.
            End Select

        End If
    End If

End Sub


-1

Runtime Code Generation with JVM and CLR - Peter Sestoft

Bu tür programlama ile gerçekten ilgilenen kişiler için çalışın.

Sizin için ipucum eğer bir şey dize önlemek için denemek, yani Alan sınıf varsa, bir dize yerine alan türü saklamak için System.Type sınıfı kullanmak daha iyidir olmasıdır . Ve yeni sınıflar yaratmak yerine en iyi çözümler uğruna yeni sınıflar yerine FiledInfo yaratılmış olanları kullanmaya çalışın .


2
Bağlantı öldü: -1
Glenn Slayden

Glenn: Hızlı bir googling çalışan bir bağlantı ortaya koydu: pdfs.semanticscholar.org/326a/…
Andreas Pardeike

@AndreasPardeike Teşekkürler, makalede tamir ettim.
Glenn Slayden
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.