2010-04-06 5 views
2

J'ai désassemblé la DLL .NET 'System' et j'ai regardé le code source des classes de variables (string, int, byte, etc.) pour voir si je pouvais comprendre comment faire une classe qui pourrait prendre une valeur. J'ai remarqué que la classe "Int32" hérite de ce qui suit: IComparable, IFormattable, IConvertible, IComparable, IEquatable.Création de votre propre classe "int" ou "string"

Les classes String et Int32 ne sont pas héritables, et je ne peux pas comprendre ce que dans ces interfaces héritées permet aux classes de contenir une valeur. Ce que je voudrais est quelque chose comme ceci:

public class MyVariable : //inherits here 
{ 
    //Code in here that allows it to get/set the value 
} 

public static class Main(string[] args) 
{ 
    MyVariable a = "This is my own custom variable!"; 
    MyVariable b = 2976; 

    if(a == "Hello") { } 
    if(b = 10) { } 
    Console.WriteLine(a.ToString()); 
    Console.WriteLine(a.ToString()); 
} 
+2

Je peux faire cette compilation, mais ... Pourquoi? –

+1

De plus, le second if manque un '=' (devrait probablement être 'b == 10') –

+1

Dans la ligne 12 du code ci-dessus, devrait-il être' if (b == 10) {} 'au lieu de' if (b = 10) {} '? –

Répondre

7

Vous pouvez le faire par overloading operators. Il y a un tutoriel sur MSDN.

Disons que vous voulez un type qui peut être une chaîne ou un entier (comme Either Haskell):

public sealed class StringOrInt32 
{ 
    private string stringValue; 
    private int int32Value; 
    private bool isString; 

    public bool IsString { get { return isString; } } 
    public bool IsInt32 { get { return !isString; } } 

    public string StringValue 
    { 
     get 
     { 
      if(!isString) throw new InvalidOperationException(); 
      return stringValue; 
     } 
    } 

    public int Int32Value 
    { 
     get 
     { 
      if(isString) throw new InvalidOperationException(); 
      return int32Value; 
     } 
    } 

    public StringOrInt32(string value) 
    { 
     isString = true; 
     stringValue = value; 
    } 

    public StringOrInt32(int value) 
    { 
     isString = false; 
     int32Value = value; 
    } 

    // Allows writing this: 
    // StringOrInt32 foo = "Hello world!"; 
    public static implicit operator StringOrInt32(string value) 
    { 
     return new MyVariable(value); 
    } 

    // Allows writing this: 
    // StringOrInt32 foo = 42; 
    public static implicit operator StringOrInt32(int value) 
    { 
     return new MyVariable(value); 
    } 

    // Allows writing this: 
    // StringOrInt32 foo = "Hello world!; 
    // string bar = (string)foo; 
    // Though foo.StringValue directly would be better 
    public static explicit operator string(StringOrInt32 value) 
    { 
     return value.StringValule; 
    } 

    // Allows writing this: 
    // StringOrInt32 foo = 42; 
    // int bar = (int)foo; 
    // Though foo.Int32Value directly would be better 
    public static explicit operator int(StringOrInt32 value) 
    { 
     return value.Int32Value; 
    } 

    public static bool operator==(StringOrInt32 left, StringOrInt32 right) 
    { 
     if(left.IsString != right.IsString) 
      return false; 
     if(left.IsString) 
      return left.StringValue == right.StringValue; 
     else 
      return left.Int32Value == right.Int32Value; 
    } 

    public static bool operator!=(StringOrInt32 left, StringOrInt32 right) 
    { 
     return !(left == right) 
    } 

    // Don't forget to override object.Equals(), object.GetHashCode(), 
    // and consider implementing IEquatable<StringOrInt32> 
    // Also, null checks, etc 
} 
+0

Merci! Comme l'une des autres réponses, c'est exactement ce que je cherchais. – afollestad

1

"int" et "string" sont des types intégrés. Vous ne serez pas en mesure d'en faire un qui n'a pas de champs discernables. Cependant, vous pouvez faire quelque chose qui ressemble et se comporte presque exactement comme un type intégré.

Le meilleur exemple est Nullable<T>. Ceci fournit une version annulable de tous les types de valeur et peut être attribué comme il est construit dans:

int? x = 0; 
int? y = null; 

La façon dont cela fonctionne est Nullable<T> l'emporte sur les moulages explicites et implicites. De cette façon, vous pouvez assigner un type intégré comme int à un Nullable<int> personnalisé et le code au sein de Nullable<int>.op_Implicit gère la conversion de manière transparente.

Voici un exemple complet:

public struct MyVariable 
{ 
    private int _intValue; 
    private string _stringValue; 

    public override bool Equals(object obj) 
    { 
     if (!(obj is MyVariable)) 
     { 
      return false; 
     } 
     var y = (MyVariable) obj; 
     return _stringValue == y._stringValue && _intValue == y._intValue; 
    } 

    public override int GetHashCode() 
    { 
     return (_stringValue ?? _intValue.ToString()).GetHashCode(); 
    } 

    public override string ToString() 
    { 
     return _stringValue ?? _intValue.ToString(); 
    } 

    public static implicit operator MyVariable(string value) 
    { 
     return new MyVariable { _stringValue = value }; 
    } 

    public static implicit operator MyVariable(int value) 
    { 
     return new MyVariable { _intValue = value }; 
    } 

    public static bool operator==(MyVariable variable, string value) 
    { 
     return variable._stringValue == value; 
    } 

    public static bool operator ==(MyVariable variable, int value) 
    { 
     return variable._intValue == value; 
    } 

    public static bool operator !=(MyVariable variable, string value) 
    { 
     return variable._stringValue == value; 
    } 

    public static bool operator !=(MyVariable variable, int value) 
    { 
     return variable._intValue == value; 
    } 

    public static void Test() 
    { 
     MyVariable a = "This is my own custom variable!"; 
     MyVariable b = 2976; 

     if (a == "Hello") { } 
     if (b == 10) { } 
     Console.WriteLine(a.ToString()); 
     Console.WriteLine(a.ToString()); 
    } 
} 
+0

Nullable a en fait une magie supplémentaire derrière les moulages que vous ne pouvez pas émuler dans C#: opérateurs levés. –

+0

Merci! Ceci est exactement ce que je cherchais. – afollestad

3

Les types intégrés en C# sont traités « spécialement » en ce que la valeur littérale 1234 dans le code est défini comme étant de type System.Int32, et la valeur littérale "some string" est définie comme étant de type System.String. Donc, afin de soutenir le type de code que vous voulez, vous devrez fournir des opérateurs de conversion qui peuvent convertir d'int et de chaîne (et tout le reste) à votre type. Jetez un oeil à la rubrique conversion operators dans MSDN.

1

Généralement, vous voulez créer un nouveau type pour stocker une nouvelle représentation de données. Dans votre exemple ci-dessus, vous utilisez MyVariable pour stocker des chaînes - quelque chose que le type de chaîne est déjà bon à faire.

Si vous souhaitez enregistrer un autre type de données, par exemple une combinaison de chaîne et int dans un endroit facile d'envoyer autour de package que vous le feriez que vous avez commencé ci-dessus:

public class MyVariable 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
} 

Puis:

MyVariable personOne = new MyVariable { Name = "John", Age = 34 }; 
MyVariable personTwo = new MyVariable { Name = "Joe", Age = 312 }; 

Interfaces tels que IComparable et IFormattable permettent à votre nouveau type d'être utilisé de manière spécifique, par exemple IComparable peut être transmis à des listes triées comme il est capable de comparer son auto à une autre instance et « classement » en conséquence .