Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I declare derived "shell" classes that do nothing but act as renames?

I have two different kinds of strings I'm passing around and using in my code, and the two are closely related, but should not be confused for one another. I thought I could help myself avoid errors by having two classes that are just strings, but with different names so that method signatures (and type incompatibility in general) would enforce the semantic meaning of the two different kinds of strings. At the same time, I didn't want to have to refactor from something = "foo"; to something.Value = "foo"; in a hundred places.

First thought:

private class FirstKind : string { }
private class SecondKind : string { }

The idea being that if I have

void MyMethod(FirstKind varOne, SecondKind varTwo) {...}

, and then try to call it with MyMethod(secondKindVar, firstKindVar);, I'd get a compiler error.

Wall I hit: string is sealed.

Second thought: create generic KindOf<T> class that would do nothing but take in and spit out the value with implicit conversion operators. Like this:

private class KindOf<T>
{
    public T Value { get; set; }
    public KindOf() { Value = default(T); }
    public KindOf(T val) { Value = val; }
    public static implicit operator T(KindOf<T> kindOf) { return kindOf.Value; }
    public static implicit operator KindOf<T>(T value) { return new KindOf<T>(value); }
}

This way I could do something like this:

private class FirstKind : KindOf<string> { }
private class SecondKind : KindOf<string> { }

Wall I hit: nothing from the KindOf<T> class seems to inherit: neither constructors nor operators exist in the derived types, meaning I have to re-implement essentially the whole base class in the derived classes. Code copying. Yuck.

So I feel like I'm missing something basic here, and maybe I'm off in the weeds. Any ideas for what I'm trying to do?

like image 543
Atario Avatar asked Jun 21 '12 14:06

Atario


3 Answers

To be honest, I wouldn't use the implicit operators at all, or any inheritance for that matter. The whole idea is to have a method that takes a FirstKind. With the implicit operator you can just pass in a string and it'll be converted; this means that you're not forcing the code to explicitly state that it's a first/second kind. I'd stick with two classes that each have just a constructor taking a string and a read only property. You're over complicating it by adding anything else.

If using that property is getting annoying, I could possibly see the implicit conversion to string, from each of the custom classes, but I do believe that an implicit conversion from string would be detrimental. While I agree copy/paste code is generally to be avoided, when it's literally a single line of code, copy-pasting that implicit conversion into both classes will be easier, more effective, and generally more maintainable than trying to use inheritance or anything else to avoid writing it twice.

like image 84
Servy Avatar answered Nov 13 '22 22:11

Servy


I'm going to make my comment an answer because I think it stands as one.

Off the top of my head, you might just need to implement the implicit operators and constructors on the subclasses. Yeah, kind of like code copying, but at least it's not employing convoluted process or using inheritance when the inheritance doesn't really communicate anything real (rhetorically, what is a "KindOf" anyway and does it really mean anything?) and is just there to reduce code duplication across classes that have nothing in common besides similar syntax (but different uses)

public class FirstKind
{
    public string Value { get; private set; }

    public FirstKind(string value)
    {
        this.Value = value;
    }

    public static implicit operator FirstKind(string value)
    {
        return new FirstKind(value);
    }

    public static implicit operator string(FirstKind value)
    {
        return value.Value;
    }
}

Also, you might want to consider making the class immutable if they're supposed to represent strings. (another implementation detail that might point to perhaps these objects should not be inheriting from a reused KindOf class which, in your implementation, makes them mutable always)

It's not much wiring and leaves your code open for changes in the future (perhaps you wish to add a new property? Add some validation code?) and consumers of the API really don't care that anything is a KindOf just to save on some minor code duplication. (perhaps you can make a snippet to make your life a bit easier?)

like image 24
Chris Sinclair Avatar answered Nov 13 '22 22:11

Chris Sinclair


In your question, FirstKind and SecondKind are the types of your string-like variables, and string is the type argument of your KindOf<T> class. A different approach (which avoids the code duplication required by the other answers) is to flip the role of FirstKind and SecondKind, making them empty "marker" classes that are used as type arguments of a String<TKind> type:

public class FirstKind { }
public class SecondKind { }

public struct String<TKind>
{
    private readonly string _value;

    public String(string value)
    {
        _value = value;
    }

    public override string ToString()
    {
        return _value;
    }

    public static implicit operator String<TKind>(string s) // see the note below
    {
        return new String<TKind>(s);
    }

    public static implicit operator string(String<TKind> s)
    {
        return s.ToString();
    }

    public static String<TKind> operator +(String<TKind> s1, String<TKind> s2)
    {
        return new String<TKind>(s1.ToString() + s2.ToString());
    }

    public int Length
    {
        get { return _value.Length; }
    }

    // You can add methods like Substring and Trim that delegate to _value.
}

Now you can declare MyMethod like this:

void MyMethod(String<FirstKind> varOne, String<SecondKind> varTwo)
{
    varOne += varOne; // OK
    varTwo += varTwo; // OK
    varOne = varTwo;  // ERROR
}

Note: I've left in the implicit conversion operator from a plain string. You may want to consider removing it, forcing code to explicitly identify the kind of string: new String<FirstKind>("...").

like image 1
Michael Liu Avatar answered Nov 13 '22 22:11

Michael Liu